XSS Moderne: DOM, Stored et Reflected avec Exemples Reels en Environnement de Test
Trois saveurs de XSS dissequees en sandbox avec payloads, flux d'exploitation et mitigations via CSP stricte, Trusted Types et sanitisation DOMPurify.

En 2025, le rapport de HackerOne a classe le XSS comme le deuxieme bug le plus rapporte dans les programmes publics, avec une mediane de 750 USD par decouverte et des pics vers 20 000 USD sur des cibles enterprise. La famille ne meurt pas parce que les navigateurs evoluent tandis que les pipelines frontend collent encore des chaines dans innerHTML sans ceremonie. L'equipe Basilisk monte un labo avec trois applications volontairement vulnerables, capture chaque requete dans Burp Suite et deroule chaque vecteur avec payload, contexte et correctif. Avant de copier quoi que ce soit, verifiez que votre labo est isole, car lancer des scripts sur des tiers sans mandat ecrit reste un delit selon la loi Godfrain et le RGPD.
Les trois familles de XSS en un coup d'oeil
Le XSS est l'injection et l'execution de JavaScript de l'attaquant dans le contexte de securite d'une origine tierce. Trois familles couvrent quasiment tous les cas: Reflected (le payload arrive dans la requete et est renvoye immediatement), Stored (le payload persiste dans le backend et frappe chaque visiteur futur) et DOM-based (l'injection se produit entierement cote client, sans que le serveur voie le payload). L'impact est le meme dans les trois cas: l'attaquant herite des privileges de la victime dans le navigateur, peut voler des jetons de session, agir au nom de l'utilisateur et, contre un admin, prendre toute l'application.
Reflected XSS
Le Reflected XSS apparait quand l'entree utilisateur revient dans la reponse HTTP sans encodage correct, generalement via querystring ou formulaire GET. Dans notre labo, une recherche sur /search?q= insere le terme dans un h2, donc le classique <svg/onload=alert(document.domain)> se declenche au contexte top-level. Ce qui separe un rapport amateur d'un rapport professionnel, c'est prouver l'impact: exfiltrer le cookie de session via fetch vers un domaine attaquant ne marche que si le cookie n'est pas HttpOnly. Capturez la faille dans l'historique de Burp, avec la requete exacte et le contexte de reponse, car un rapport sans requete reproductible atterrit dans la pile des doublons ou des informatifs.
Stored XSS
Le Stored XSS est la variante la plus dangereuse car il reside dans la base et frappe chaque visiteur futur. Sur DVWA on regle le niveau medium, on colle dans le champ commentaire le payload <img src=x onerror=...> qui encode le cookie en base64 et l'envoie a un webhook, et on voit le webhook collecter des sessions de moderateurs en quelques secondes. La surface reelle inclut les rendus Markdown, les modeles d'email transactionnel, les exports CSV ouverts dans Excel et meme les metadonnees EXIF lues par un tableau de bord interne. La seule defense robuste combine sanitisation en entree et echappement en sortie; l'un sans l'autre est du theatre, car une valeur stockee proprement se declenche quand meme dans le mauvais contexte.
DOM-based XSS
Le DOM-based XSS se produit entierement dans le navigateur, donc le payload ne touche jamais les logs serveur. Le classique location.hash canalise vers document.write subsiste dans les widgets de chat legacy et dans les apps React qui passent les donnees du hash a dangerouslySetInnerHTML. On adapte le niveau 1 du Google XSS Game dans le labo et on demontre la chasse aux sinks en ouvrant les DevTools, en activant 'Pause on exceptions' et en lachant l'extension DOM Invader de Burp. Le flux de triage est toujours le meme: reperer la source (hash, search, postMessage), la tracer jusqu'au sink (innerHTML, eval, setAttribute), valider avec un payload minimal, puis escalader. Comme le serveur ne voit rien, les WAF cote serveur sont aveugles ici.
Le contexte est tout: le bon echappement
Le meme payload se declenche ou echoue selon le contexte d'injection. Le corps HTML exige un encodage d'entites HTML, une valeur d'attribut ajoute la gestion des guillemets, un bloc <script> exige l'echappement de chaine JavaScript, une URL exige un encodage sensible au contexte, et un contexte de style a ses propres regles. La racine de presque tout XSS est une valeur qui passe d'un contexte a un autre sans etre reencodee. C'est pourquoi la regle OWASP est: encoder en sortie, adapte au contexte, jamais filtrer de facon generique en entree. Un filtre en liste noire qui ne retire que les chevrons tombe aussitot face aux vecteurs d'attribut comme onmouseover.
Monter le laboratoire
Montez aujourd'hui le labo avec DVWA, OWASP Juice Shop et une app Next.js allegee, le tout en conteneurs Docker sur un reseau isole sans route vers internet sauf un webhook controle pour la preuve d'exfiltration. Routez tout le trafic navigateur par Burp, activez le logging de l'historique proxy et construisez une collection Repeater par vecteur. Reproduisez les trois vecteurs jusqu'a ce que chacun declenche un popup, et notez pour chacun la requete exacte, le contexte d'injection et le fragment de reponse. Ce runbook est ensuite l'ossature de rapports de bug bounty propres avec reproduction en une phrase.
Mitigation moderne: CSP, Trusted Types, DOMPurify
La mitigation moderne a cesse de parler de filtrer les chevrons il y a des annees. Content Security Policy niveau 3 avec un nonce par requete tue l'injection inline meme quand un attaquant force du HTML sur la page, a condition de ne pas ceder et d'ajouter unsafe-inline en fallback. Trusted Types transforme les affectations a innerHTML en type error sauf si elles passent par une policy enregistree. Combine avec DOMPurify pour le HTML riche, les mesures de Google montrent environ 90% de reduction de surface. HttpOnly sur le cookie de session neutralise le vol classique de cookie, et SameSite attenue le renvoi du credential vers des origines tierces.
Shift-left: attraper le XSS en CI
Cablez la detection en CI en lancant Semgrep avec javascript.lang.security.audit.xss sur chaque PR, complete par des plugins ESLint qui signalent dangerouslySetInnerHTML et les affectations brutes a innerHTML. Un passage DAST contre l'instance de staging attrape ce que l'analyse statique manque, comme les parametres reflechis dans des modeles tiers. L'essentiel est qu'un nouveau sink casse le build, pas seulement produise un rapport que personne ne lit. C'est ainsi que la prevention XSS devient partie de la Definition of Done au lieu d'un pentest tardif.
Pieges frequents
Le premier piege est la liste noire: tout filtre qui ne bloque que des payloads connus tombe face aux variantes d'encodage et aux gestionnaires d'evenements alternatifs. Le deuxieme est de traiter une WAF comme seule defense, contournee par le melange de casse, les commentaires et le double encodage. Le troisieme est une CSP avec unsafe-inline, qui en pratique ne bloque rien. Le quatrieme est de supposer qu'un framework comme React est sur par defaut alors que dangerouslySetInnerHTML et l'injection dans href restent grands ouverts. Le cinquieme est l'absence de HttpOnly, qui transforme chaque faille reflechie directement en vol de session.
Checklist
Testez chaque point d'injection dans les cinq contextes (corps HTML, attribut, script, URL, style). Confirmez si le cookie de session a HttpOnly et SameSite. Verifiez la CSP: pas d'unsafe-inline, nonce par requete, script-src restrictif. Testez si Trusted Types est applique. Prouvez l'impact avec une demonstration inoffensive mais sans ambiguite (un popup avec document.domain ou un callback sans donnee reelle). Documentez la reproduction en une phrase, l'impact et un correctif suggere par constat, et mappez chaque constat aux buckets STRIDE Tampering et Elevation avant d'ouvrir le ticket.
Vecteurs avances au-dela du popup
Un alert(1) prouve l'injection, mais un rapport mature montre la chaine derriere. Avec un XSS stable, on peut lire les jetons CSRF directement dans le DOM et emettre des requetes au nom de la victime, defaisant la defense CSRF habituelle car la requete nait de l'origine legitime. Via l'API Fetch, le payload peut interroger des endpoints authentifies et exfiltrer les reponses, y compris des donnees de profil ou des panneaux d'admin. Un service worker enregistre via XSS survit meme au rechargement de la page et intercepte le trafic futur. Particulierement sous-estimee est la combinaison d'un XSS et d'un handler postMessage ouvert qui accepte des donnees a travers les frontieres de frame: un seul controle d'origine manquant transforme un sous-domaine inoffensif en tete de pont. C'est pourquoi on n'evalue jamais l'impact par le popup, mais par l'action realistement atteignable: prise de compte, exfiltration de donnees ou escalade de privilege vers un admin, chacune avec une preuve demontree mais non nuisible.
Reporting et divulgation responsable
Un constat techniquement correct sans rapport propre s'evapore. La structure triee sur HackerOne, Bugcrowd et Intigriti est toujours la meme: un resume precis, l'URL et le parametre affectes, une reproduction en une phrase avec le payload exact, un impact demontre et une suggestion concrete de correctif. La preuve d'impact reste deliberement inoffensive: un popup avec document.domain ou un callback sans donnee reelle d'utilisateur, jamais une exfiltration massive de sessions vivantes. Captures d'ecran et un court clip video accelerent nettement la triage. Tenez-vous strictement au scope du programme; un hit hors scope n'est pas une gloire, c'est un risque juridique. Documentez la justification de la chaine de vecteur CVSS pour que la severite ne paraisse pas negociable. Un rapport qui fait le travail du defenseur, comprendre et deployer le correctif, est paye plus vite et construit la reputation qui mene ensuite a des invitations privees avec des bounties plus eleves.
FAQ : Un framework moderne rend-il le XSS impossible ?
Non. React, Angular et Vue echappent automatiquement les bindings standard, mais laissent des trappes deliberees ouvertes: dangerouslySetInnerHTML, v-html, bypassSecurityTrustHtml et les attributs href/src portant des URLs javascript:. Ces trappes sont la source la plus frequente de XSS dans les bases modernes. Le framework reduit la surface mais ne remplace pas CSP, Trusted Types et l'echappement sensible au contexte.
FAQ : Une WAF suffit-elle contre le XSS ?
Non. Une WAF est une couche externe utile, mais elle est contournable par encodage, melange de casse et astuces au niveau protocole, et elle ne voit pas du tout le XSS DOM-based car le payload n'atteint jamais le serveur. Traitez la WAF comme alerte precoce et reduction de bruit, jamais comme remplacement de l'echappement en sortie et d'une CSP stricte.
Conclusion
Conclusion pratique: montez le labo, reproduisez les trois vecteurs jusqu'a declencher un popup sur chacun, puis ajoutez une CSP a nonce, Trusted Types et DOMPurify et rejouez les memes payloads. Ce qui se declenche encore merite un ajustement de policy jusqu'a ce que ca casse. Suivez chaque iteration dans un runbook personnel car les meilleurs rapports XSS exigent une reproduction en une phrase, un impact demontre et un correctif suggere. Le XSS n'est pas un probleme resolu, c'est une discipline: encoder par contexte, empiler la defense en profondeur et traiter chaque nouvelle sortie comme un sink potentiel jusqu'a preuve du contraire.


