XSS: reflected vs stored vs DOM XSS
XSS: reflected vs stored vs DOM XSS
Difference majeure en une phrase
La difference la plus importante est celle-ci:
Reflected XSSetStored XSSdisent surtout comment le payload arrive jusqu'a la victime.DOM XSSdit surtout ou se trouve la faille: dans le JavaScript du navigateur qui lit une donnee controlable puis l'injecte dans le DOM de maniere dangereuse.
Autrement dit:
Reflected= une seule requete/reponse, non persistant.Stored= le payload est enregistre puis rejoue plus tard.DOM= le navigateur fabrique lui-meme l'execution a cause du code frontend.
Le point que beaucoup ratent
On presente souvent ces trois noms comme trois categories au meme niveau, mais la realite est un peu plus subtile.
OWASP explique le sujet comme une matrice:
- Axe 1:
ReflectedvsStored= comment la donnee malveillante arrive. - Axe 2:
Server-sidevsClient-side= ou la logique vulnerable vit.
Dans cette logique:
DOM XSSest generalement une forme deClient-side XSS.- Donc
DOM XSSne repond pas exactement a la meme question queReflectedouStored.
Exemple important:
- Un payload dans
location.searchlu parinnerHTMLest souvent vu comme unDOM XSSnon persistant. - Un commentaire stocke en base puis reinjecte via
innerHTMLpar le frontend peut etre a la foisStoredetDOM.
Pour debuter, retiens ceci:
ReflectedetStored= mode de livraison.DOM= lieu de la transformation dangereuse.
Tableau mental rapide
- Lieu d'entree: URL, parametre GET/POST, header HTTP, champ de recherche, page d'erreur.
- Lieu de la faille: rendu serveur ou template HTML qui renvoie l'entree sans encodage.
- Persistance: non.
- Condition d'exploitation: la victime doit charger une requete piegee.
- Endroits classiques: recherche, messages d'erreur, page de login, page 404, filtres, tri, pagination.
Reflected XSS
- Lieu d'entree: commentaire, profil, biographie, ticket support, chat, wiki, CMS, forum, champs admin.
- Lieu de stockage: base de donnees, cache, fichier, queue, back-office.
- Lieu de la faille: affichage ulterieur de la valeur stockee sans protection adaptee.
- Persistance: oui.
- Condition d'exploitation: il suffit qu'une victime consulte la donnee stockee.
- Endroits classiques: commentaires, profils, tableaux de bord admin, journaux, outils internes.
Stored XSS
- Lieu d'entree:
location.search,location.hash,document.URL,document.referrer,window.name,postMessage,localStorage,sessionStorage. - Lieu de la faille: JavaScript du navigateur.
- Persistance: pas par nature; souvent non persistant, parfois persistant si la source est stockee.
- Condition d'exploitation: le JS client doit lire une source controlable puis l'envoyer vers un sink dangereux.
- Endroits classiques: SPA, routeurs base sur
#, pages de preview, widgets, pages de redirection, composants qui utilisentinnerHTML.
DOM XSS
Le "lieu" exact de la vulnerabilite
Si tu veux savoir ou chercher dans une vraie application:
- Cherche le passage direct de
request -> HTML response. - Cote code:
req.query,req.body,$_GET,$_POST,request.getParameter(...)qui finissent dans le HTML.
Reflected XSS
- Cherche le passage
input utilisateur -> stockage -> affichage. - Cote code: create/update profil, commentaires, messages, notes, formulaires support, puis rendu dans une autre page.
Stored XSS
- Cherche le passage
source controllable par l'attaquant -> sink dangereux DOM. - Cote code: lecture de
location,postMessage,storage, puis usage deinnerHTML,outerHTML,document.write,insertAdjacentHTML,eval,setTimeout(string).
DOM XSS
Exemple 1: Reflected XSS
Cas realiste:
Une page de recherche affiche le terme cherche par l'utilisateur.
Code vulnerable:
<?php
$q = $_GET['q'] ?? '';
echo "<h1>Resultats pour: " . $q . "</h1>";
?>
Requete piegee:
/search.php?q=<script>alert('XSS')</script>
Pourquoi ca execute:
- Le parametre
qvient de la requete. - Le serveur le remet directement dans la reponse HTML.
- Le navigateur le traite comme du code actif, pas comme du texte.
Scenario d'attaque:
- L'attaquant fabrique une URL piegee.
- Il l'envoie par mail, chat, phishing, reseau social ou message interne.
- La victime clique.
- Le site vulnerable renvoie le payload.
- Le navigateur de la victime l'execute dans le contexte du site legitime.
Ce qu'il faut retenir:
- Le payload ne reste pas dans l'application.
- Il voyage dans une seule boucle
requete -> reponse. - Sans clic, sans ouverture de lien, il n'y a souvent pas d'exploitation.
Autres lieux frequents:
- Page 404 qui reaffiche l'URL demandee.
- Message "utilisateur X introuvable".
- Filtres de recherche ou de tri affiches dans l'interface.
- Pages de debug ou de confirmation.
Exemple 2: Stored XSS
Cas realiste:
Un site permet de publier des commentaires.
Flux vulnerable:
- L'attaquant poste un commentaire contenant du HTML/JS.
- Le serveur l'enregistre en base.
- Plus tard, tout lecteur de la page charge ce commentaire.
- Le navigateur execute le payload.
Code vulnerable cote rendu:
<?php foreach ($comments as $comment): ?>
<div class="comment"><?= $comment['body'] ?></div>
<?php endforeach; ?>
Payload d'exemple:
<img src=x onerror=alert('stored-xss')>
Pourquoi c'est souvent plus grave:
- Le payload est persistant.
- Une seule soumission peut toucher plusieurs victimes.
- Les admins, moderateurs et utilisateurs privilegies consultent souvent ces zones.
- Il n'y a pas besoin d'envoyer un lien specifique a chaque victime.
Lieux classiques de Stored XSS:
- Commentaires, avis, messages prives, signatures, bios.
- Tickets support et interfaces de back-office.
- Journaux consultes via une interface web.
- Noms de fichiers ou metadonnees affiches dans un panneau d'admin.
Variante importante:
Blind XSSest une forme deStored XSS.- Le payload est stocke puis execute plus tard dans un back-office que l'attaquant ne voit pas directement.
- Exemple: formulaire de contact consulte ensuite par un agent support dans l'admin.
Exemple 3: DOM XSS
Cas realiste:
Une page frontend lit un parametre de l'URL puis l'injecte dans la page.
Code vulnerable:
<div id="welcome"></div>
<script>
const params = new URLSearchParams(window.location.search);
const name = params.get("name") || "invite";
document.getElementById("welcome").innerHTML = "Bonjour " + name;
</script>
URL piegee:
/welcome.html?name=<img src=x onerror=alert('dom-xss')>
Pourquoi ca execute:
- Le navigateur lit
location.search. - Le code JS traite cette valeur comme du HTML.
innerHTMLconstruit de vrais noeuds DOM a partir de la chaine.- Le payload s'execute sans que le serveur ait besoin de renvoyer ce script dans le HTML initial.
Le point central:
- Le serveur peut renvoyer une page "propre".
- C'est le JavaScript du client qui rend la page dangereuse.
Autres sources frequentes:
location.hashdocument.URLdocument.referrerwindow.namepostMessagelocalStorageetsessionStorage
Sinks dangereux frequents:
element.innerHTMLelement.outerHTMLdocument.write(...)insertAdjacentHTML(...)eval(...)setTimeout("code")setInterval("code")
Exemple de DOM XSS via hash:
<div id="out"></div>
<script>
document.getElementById("out").innerHTML = location.hash.slice(1);
</script>
Avec:
/page.html#<svg onload=alert('hash-xss')>
Ici, le fragment #... n'est meme pas envoye au serveur: tout se passe dans le navigateur.
Difference pratique entre les trois
Si on pose la question "ou est la faute principale ?":
- Le serveur renvoie immediatement une entree non protegee.
Reflected XSS
- L'application enregistre une entree malveillante puis la reaffiche plus tard.
Stored XSS
- Le code JavaScript client prend une source controlable et la pousse vers un sink dangereux.
DOM XSS
Si on pose la question "combien de temps vit le payload ?":
Reflected= une interaction.Stored= persistant.DOM= variable; souvent non persistant, parfois persistant si la source est stockee.
Si on pose la question "faut-il pieger chaque victime avec un lien ?":
Reflected= souvent oui.Stored= souvent non.DOM= souvent oui quand la source est dans l'URL; pas forcement si la source vient d'un stockage ou d'une API.
Exemple compare avec le meme besoin fonctionnel
Imagine une fonctionnalite "Bienvenue <nom>".
- Le serveur lit
?name=...et l'injecte dans la reponse HTML.
- Version
Reflected
- Le nom vient d'un profil sauve en base puis affiche a chaque visite.
- Version
Stored
- Le frontend lit
?name=...ou#name=...et le met dansinnerHTML.
- Version
DOM
Le resultat visible peut sembler identique, mais le point de correction n'est pas le meme.
Comment les detecter pendant un test
- Injecter une charge dans chaque parametre.
- Observer si la charge revient dans la reponse.
- Chercher ou elle apparait: texte, attribut HTML, script inline, URL.
- Pour
Reflected XSS
- Injecter dans tous les champs qui sont sauvegardes.
- Parcourir ensuite les pages qui reaffichent ces valeurs.
- Ne pas oublier les vues admin, moderation, export, logs, emails HTML.
- Pour
Stored XSS
- Lire le JavaScript et tracer les
sourcesetsinks. - Modifier URL, hash, referrer, message, localStorage.
- Ouvrir les DevTools et observer la construction du DOM apres chargement.
- Pour
DOM XSS
Prevention: ce qui change selon le type
- Faire l'encodage de sortie selon le contexte HTML.
- Utiliser des templates avec auto-escaping.
- Ne jamais reinjecter brut une donnee non fiable dans HTML, attributs, JS ou URL.
- Pour
ReflectedetStored
- Remplacer
innerHTMLpartextContentquand on veut afficher du texte. - Utiliser
createElement,appendChild,setAttributeavec des noms d'attributs fixes. - Eviter
document.write,eval,setTimeout(string),insertAdjacentHTMLsur donnees non fiables.
- Pour
DOM XSS
- Valider les formats attendus, mais ne pas confondre validation et protection XSS.
- Si du HTML utilisateur est autorise, utiliser un sanitiseur robuste adapte a ce besoin.
- Ajouter une
Content-Security-Policycomme defense en profondeur, pas comme defense unique. - Mettre
HttpOnlysur les cookies pour reduire certains impacts, meme si ca ne supprime pas la faille.
- Pour tous les XSS
Les erreurs classiques de raisonnement
- Faux. Une base peut stocker du contenu malveillant.
- "Si c'est en base, c'est safe."
- Faux.
DOMparle du lieu de la faille;reflected/storedparlent du mode d'arrivee du payload.
- "DOM XSS n'a rien a voir avec reflected/stored."
- Faux. Beaucoup de payloads n'utilisent pas la balise
<script>.
- "Si on filtre
<script>, il n'y a plus de XSS."
- Insuffisant. Il faut proteger la sortie et les sinks, pas seulement l'entree.
- "Le frontend valide deja."
Resume ultra-court
- Non persistant.
- Arrive par la requete.
- Le serveur le renvoie tout de suite.
Reflected XSS
- Persistant.
- Arrive par une zone de stockage.
- Le serveur ou le frontend le rejoue plus tard.
Stored XSS
- La faute est dans le JavaScript client.
- Une source controlable arrive dans un sink DOM dangereux.
- Le serveur peut etre "propre" au depart.
DOM XSS
References principales
Synthese faite a partir de sources OWASP consultees le 2026-03-13:
- https://owasp.org/www-community/attacks/xss/
- OWASP - Cross Site Scripting (XSS)
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- OWASP - Cross Site Scripting Prevention Cheat Sheet
- https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html
- OWASP - DOM based XSS Prevention Cheat Sheet
- https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/01-Testing_for_Reflected_Cross_Site_Scripting
- OWASP WSTG - Testing for Reflected Cross Site Scripting
- https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/02-Testing_for_Stored_Cross_Site_Scripting
- OWASP WSTG - Testing for Stored Cross Site Scripting
- https://owasp.org/www-project-web-security-testing-guide/v41/4-Web_Application_Security_Testing/11-Client_Side_Testing/01-Testing_for_DOM-based_Cross_Site_Scripting
- OWASP WSTG - Testing for DOM-Based Cross Site Scripting