Faille Apache Struts : ce qu’on a oublié de vous dire

Sécurité des applications
Faille Apache Struts : ce qu’on a oublié de vous dire

La vulnérabilité affectant Apache Struts a fait couler beaucoup d’encre et fait encore parler d’elle. Chaque jour de nombreuses attaques sont détectées par notre SOC. Le vecteur d’exploitation le plus répandu reste l’attaque par injection de code dans l’entête « Content-type », dont voici un exemple :

S'il s'agit du vecteur d'attaque le plus répandu, il en reste le moins dangereux lorsqu'on dispose d'un WAF. En effet, un firewall applicatif correctement configuré bloquera immédiatement cette attaque - puisque l'entête "Content-Type" n'est évidemment pas valide (l'analyse protocolaire est l'une des fonctionnalités de base d'un WAF).

Le « problème » est qu’il existe d’autres vecteurs d’exploitation. Un article technique détaillé propose par exemple une exploitation utilisant une injection de code au niveau du corps de la requête (l’exemple proposé ne fonctionne pas en l’état, une coquille a été glissée pour éviter les script-kiddies). Dans cet exemple, l’auteur forge une requête POST de type « upload de fichier » et injecte du code à la place dudit fichier. 

Là il semble que les WAF puissent se faire berner : Il reste possible d’écrire des règles de détection visant à identifier l’injection de commandes – mais cela n’est pas une science exacte, tant les possibilités d’injection sont diverses et variées. Sans citer de marque particulière, vous irez voir les bulletins de sécurité et les articles rédigés pour l’occasion : Ils proposent pour la plupart d’écrire des règles de détection pour contrer cette attaque. En d’autres termes : Les WAF étaient aveugles sur cette faille et l’exploitation était donc possible tant qu’aucune règle de blocage n’était explicitement créée !

Puisque les WAF sont inefficaces, par défaut, pour bloquer cette attaque, il n’y aurait donc pas d’autre choix que de patcher Apache Struts. Tant pis pour vous si votre progiciel métier ne peut pas se mettre à jour, faute de support ou faute de compétence dans vos équipes (ou dans celle de l’éditeur, ce qui est fréquent sur les progiciels spécialisés sur des marchés de niche – que l’on trouve à foison dans les collectivités et les organismes publiques). 

Rassurez-vous, même si vous lirez le contraire, il existe des solutions pour vous protéger contre cette faille, et surtout contre les suivantes que l’on ne connaît pas encore. Il faut juste changer de paradigme, passer la tête par-dessus le guidon et, surtout, se tourner vers les techniques de 2017 – et abandonner celles des années 90.
 

 

 …tout dépend de votre WAF : Démonstration !

On commence par valider qu’Apache Struts est bien vulnérable via une injection de code dans l’entête HTTP, en utilisant un petit exploit disponible sur ExploitDB :

Notre serveur est bien vulnérable, nous accédons ici au fichier contenant les utilisateurs / mots de passe de Tomcat.

Ensuite on teste cette attaque en passant par un « WAF de 2017 » (Vulture dans cet exemple). Pour ce premier test, nous avons désactivé la protection par défaut visant à bloquer toute entête content-type qui n’est pas valide. Si cette protection était active, l’attaque serait bloquée immédiatement : pas pratique pour notre démonstration ! Idem pour la réputation, dans l’exemple précédent, la connexion venait de Russie (Saint Petersburg) et a une réputation de « spammer », elle serait donc bloquée par défaut avec un contrôle sur la réputation.

Vulture est ici configuré simplement avec ses règles de base (VultureRS) et les règles basiques de l’OWASP (règles CRS) :

En complément, un apprentissage par machine learning (SVM 2 à SVM 5) est activé :

L’apprentissage par whitelist (mod_defender) n’a pas été activé pour les mêmes raisons que précédemment, il aurait immédiatement bloqué la requête compte-tenu des caractères spéciaux dans l’entête. Enfin, le score de blocage de Vulture est positionné à « haut » (les requêtes ayant un score de 10 seront bloquées. Cela correspond à environ 70% du niveau de blocage maximal).

Une fois la protection en place, nous relançons notre script et constatons que l’attaque est bien bloquée (code d’erreur 403 – FORBIDDEN) :

Sans qu’il y ait eu besoin d’écrire la moindre règle spécifique à cette faille, on constate que le WAF a bloqué la requête pour plusieurs raisons :

  • Analyse protocolaire
    La requête ne contient pas tous les éléments recommandés par la RFC et présente des caractères spéciaux (score interne : +8)
  • Analyse comportementale
    L’entête « User-Agent » de la requête ne correspond pas à un navigateur connu (score interne : +2)
  • Score total : 10 sur un maximum de 10 

C’est tout juste, mais la requête est bien bloquée. Il est intéressant de noter que mod_security seul n’aurait pas bloqué cette requête (sauf à configurer Vulture en mode « Paranoïd », mais cela risquerait d’entraîner des faux-positifs avec les règles OWASP par défaut). Si l’entête « User-Agent » était connu et si la requête respectait la RFC.

Nous rejouons donc exactement la même attaque, mais cette fois en utilisant un entête « User-Agent » connu :  

La requête est toujours bloquée, pour les raisons suivantes :

  • Analyse protocolaire
    La requête ne contient pas tous les éléments recommandés par la RFC et présente des caractères spéciaux (score interne : +8)
  • Machine learning
    SVM 3 (distance de Levenshtein) a détecté une URL inhabituelle pour une requête (score interne : +1)
    SVM 4 a détecté une anomalie dans la quantité de données reçues par rapport au code HTTP de la réponse (score interne : +2)
  • Score total : 11 sur un maximum de 10

Le machine learning détecte une anomalie en entrée (SVM3), ce qui permet de porter le score à 9. Ce n’est pas suffisant pour bloquer la requête : Elle a donc atteint le serveur ! Toutefois, le machine learning détecte une anomalie en sortie (SVM4), ce qui porte le score à 11 et a pour effet de mitiger la vulnérabilité : L’attaquant n’a donc aucun retour sur le résultat de son attaque.

Rappelons qu’avec les protections de réputation et d’analyse de l’entête «content-type», l’attaque serait purement et simplement bloquée dès la réception de la requête.

 


Jusqu’ici, tout va bien : Allons plus loin !

A ce stade, des règles de bases et un apprentissage de base permettent de nous protéger efficacement. Passons maintenant à une attaque plus élaborée. Cette fois nous allons tenter une injection de code directement dans le contenu de la requête. Nous avons conçu un script Python pour l’exploiter : Le payload est celui présenté dans l’article cité précédemment : L’attaque consiste à ajouter une entête HTTP dans la réponse du serveur (« X-Struts-Exploit-Test »).

Comme précédemment, nous commençons par valider que l’attaque fonctionne :

Pour cette attaque, nous avons également veillé à faire une requête HTTP qui respecte cette fois les RFC (nous avons ajouté l’entête « Accept ») et présente un entête « User-Agent » valide.

L’attaque est bien bloquée :

Et voici ce que nous obtenons dans Vulture :

Sans qu’il y ait eu besoin d’écrire la moindre règle spécifique à cette attaque, on constate que le WAF bloque la requête pour plusieurs raisons :

  • Analyse protocolaire
    Le corps de la requête contient des éléments dangereux : NULL byte dans le BODY (score interne : +4)
    Erreur de formatage du contenu multipart (score interne : +4)
  • Analyse comportementale
    La protection CSRF détecte l’absence d’un token valide dans la requête POST (score interne : +2)
  • Score total : 10 sur un maximum de 10

L’attaque est toujours bloquée, et aucune règle n’a été spécifiquement créée. Si l’attaquant prenait la peine de récupérer le jeton CSRF et de le rejouer, la requête aboutirait – mais serait bloquée par le machine learning en sortie, comme illustré dans le scénario précédent.

On remarque qu’en activant mod_defender, avec un apprentissage par liste blanche, le score reste de 10, mais le blocage s’effectue beaucoup plus tôt dans le traitement de la requête : Vulture renvoi un code d’erreur 400 en précisant que le body est invalide :

Ce que nous voulions démontrer ici c’est qu’un simple usage de techniques modernes - ici un apprentissage de la normalité par un profil de machine learning issu d’une phase de recette préalable sur notre application, épaulé par un jeu de règles minimalistes pour valider la conformité des requêtes aux RFC – permet d’avoir un niveau de sécurité plus que correct. 

Si l’on couple le machine learning avec des whitelist (mod_defender), on obtient une protection très performante, y compris contre les menaces inconnues. Est-ce parfait ? Certainement pas, une vulnérabilité affectant le WAF directement ou une faille au niveau d’un protocole réseau pourrait avoir raison de n’importe quelle protection. Ceci étant dit, pour réussir une attaque applicative « parfaite », l’attaquant doit :

  • Contourner l’authentification éventuellement en place au niveau du WAF (désactivée dans nos exemples)
  • Contourner l’analyse protocolaire (partiellement désactivée dans nos exemples)
  • Contourner le contrôle de la réputation de l’IP source (désactivé dans nos exemples)
  • Contourner le contrôle sur la géo-localisation de l’IP source (désactivé dans nos exemples)
  • Contourner les règles de base mod_security
  • Contourner les règles de base de vulture (csrf, session hihjacking, …)
  • Contourner le filtrage par liste blanche de vulture (mod_defender)
  • Contourner le filtrage par machine learning (mod_svm)

Rien n’est parfait et une intrusion réussie est évidemment toujours possible, mais elle n’est probablement pas à la portée du premier venu lorsque le WAF est correctement configuré.

Est-ce suffisant ? Certainement pas, l’analyse permanente des logs du WAF doit permettre de détecter tout comportement déviant ou toute tentative d’exploitation plus « sournoise ». Le SOC doit s’en servir et réagir immédiatement pour renforcer le niveau de protection si nécessaire. Nous proposerons prochainement un article sur ce sujet en montrant comment détecter les prémisses d’attaques Web grâce au WAF et à l’analyse de ses logs.

En conclusion, patcher ses applications Web vulnérables est indispensable dès lors qu’une vulnérabilité apparaît. Mais il est possible de gérer cela sereinement pour ne pas avoir à réagir en « mode urgence » à chaque fois qu’une nouvelle vulnérabilité affecte vos infrastructures Web. 

Pour cela il faut des solutions capables de bloquer des menaces jusqu’alors inconnue, cela implique de ne pas uniquement se reposer sur des règles de détection, qui auront toujours une longueur de retard sur les attaquants. Le machine learning et l’analyse comportementale sont des outils permettant de commencer à inverser cette tendance. 

 

Jérémie Jourdin, Responsable R&D, Advens