SIGSEGv2 Quals: Theory of Browser Evolution
Challenge web d’exploitation d’une mXSS : bypass de DOMPurify et d’une blacklist de mots-clés.
Je n’ai pas pensé à sauvegarder l’énoncé, mais c’était plus ou moins :
Tu ne parviendras jamais à dérober mon biscuit de session, vil faquin.
http://qual-challs.rtfm.re:8080
L’admin utilise la dernière version de Chrome.
CTRL+U
La page web ressemble à ceci.
Et le code source :
|
|
Pour synthétiser :
- Notre payload passe en GET via le paramètre
layout
- Il est filtré à l’aide de DOMPurify (https://github.com/cure53/DOMPurify)
- Le webmestre rajoute une couche avec une blacklist maison de mots-clés
- Le payload est ensuite inséré en tant que code HTML dans la div “injection”
- Le code HTML de cette div est assigné à lui-même
Notre but est donc de bypass le DOMPurify ainsi que la blacklist afin d’exécuter du code JS arbitraire et voler le cookie de l’admin.
Bypass DOMPurify
Le premier truc qui fait tilt c’est cette ligne.
|
|
Elle nous indique déjà quel type de XSS on va devoir exploiter : une mXSS (mutation XSS).
Le principe est le suivant : on va tirer profit du fait que les navigateurs n’aiment pas le code HTML mal formé, et qu’ils préfèrent le remplacer par du code HTML valide. On appelle ça la mutation. Un exemple totu simple :
Ici, Chromium a pris la liberté de rajouter une balise fermante parce que sinon le HTML est tout cracra. C’est ce mécanisme qu’exploite une mXSS.
En cherchant sur la toile une façon de contourner DOMPurify via mXSS, je suis tombé sur cet excellent blog post.
J’en ai extrait le payload suivant, permettant de bypass DOMPurify dans un navigateur Chromium.
|
|
Ici, c’est donc <img src=1 onerror=alert(1)>
, placé en attribut id
d’une balise a
qui va trigger la XSS après mutation par le navigateur.
En l’état, on sait déjà que ce bout de code ne nous permettra pas de flag car on retrouve la balise img
qui est blacklistée. J’ai donc répliqué rapidement la page web en local, en supprimant tout ce qui touche à la fonction nuclearSanitizer
, on cherchera un vecteur non filtré plus tard.
|
|
Je lance un petit serveur PHP pour accéder rapidement à ma page.
$ php -S 0:8000
Et dans Chromium (fraîchement installé) :
http://127.0.0.1:8000/?layout=<svg></p><style><a id="</style><img src=1 onerror=alert(1)>">
Youpi, ça marche.
En jetant un oeil au code source de la page :
Les balises style
et p
se sont d’abord respectivement fermée et ouverte “sur elles-mêmes”. Ensuite, on peut voir que DOMPurify a laissé passer ce qu’il y a dans l’attribut id
de a
sans broncher, alors que le navigateur, à défaut de trouver une balise fermante style
quelque part, a parsé celle dans l’attribut id
. Du coup, la balise img
se retrouve intégrée dans le DOM, et n’est plus une simple valeur d’attribut.
Tout ça a lieu au moment de l’exécution du code suivant
|
|
, qui est la dernière ligne du script. Donc DOMPurify a loupé le coche, la balise est injectée et on a bypassé le premier filtre.
Bypass blacklist
Il nous reste à bypass la blacklist, qui nous enlève quand même beaucoup de possiblités si on ne veut pas d’interaction utilisateur. De plus, elle est insensible à la casse, donc il s’agit juste de trouver la bonne balise avec le bon attribut.
C’est le moment de sortir la super cheatsheet que PortSwigger a sorti récemment ! (https://portswigger.net/web-security/cross-site-scripting/cheat-sheet). Elle permet de trier les payloads selon l’attribut que l’on veut, l’événement à exploiter et/ou sur quel navigateur on veut trigger la XSS.
En fouillant un peu parmi les vecteurs proposés, on se tourne rapidement vers la balise svg
, très souvent utilisée car riche en options. Voici par exemple ce qu’on nous propose.
A priori, rien de tout ça n’est filtré ! On remplace <img src=1 onerror=alert(1)>
dans notre payload par <svg><set onbegin=alert(1) attributename=x dur=1s>
, ce qui nous donne :
|
|
Balançons ça sur le serveur.
Ça passe carrément, plus qu’à modifier le alert(1)
par :
|
|
et à re-balancer.
On est automatiquement redirigé vers le Request Inspector, c’est win.
Pour éviter de surcharger le bot admin, le serveur utilisé depuis le début n’est fait que pour tester son payload. Ce dernier est à soumettre sur une page tierce à laquelle je n’ai plus accès maintenant que le CTF est fini, mais une fois l’URL envoyée au bot il fait sa requête instantanément.