Accueil / retour à la liste

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:

  1. Reflected XSS et Stored XSS disent surtout comment le payload arrive jusqu'a la victime.
  2. DOM XSS dit 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:

  1. Reflected = une seule requete/reponse, non persistant.
  2. Stored = le payload est enregistre puis rejoue plus tard.
  3. 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:

  1. Axe 1: Reflected vs Stored = comment la donnee malveillante arrive.
  2. Axe 2: Server-side vs Client-side = ou la logique vulnerable vit.

Dans cette logique:

  1. DOM XSS est generalement une forme de Client-side XSS.
  2. Donc DOM XSS ne repond pas exactement a la meme question que Reflected ou Stored.

Exemple important:

  1. Un payload dans location.search lu par innerHTML est souvent vu comme un DOM XSS non persistant.
  2. Un commentaire stocke en base puis reinjecte via innerHTML par le frontend peut etre a la fois Stored et DOM.

Pour debuter, retiens ceci:

  1. Reflected et Stored = mode de livraison.
  2. DOM = lieu de la transformation dangereuse.

Tableau mental rapide

  1. Reflected XSS
  1. Stored XSS
  1. DOM XSS

Le "lieu" exact de la vulnerabilite

Si tu veux savoir ou chercher dans une vraie application:

  1. Reflected XSS
  1. Stored XSS
  1. 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:

  1. Le parametre q vient de la requete.
  2. Le serveur le remet directement dans la reponse HTML.
  3. Le navigateur le traite comme du code actif, pas comme du texte.

Scenario d'attaque:

  1. L'attaquant fabrique une URL piegee.
  2. Il l'envoie par mail, chat, phishing, reseau social ou message interne.
  3. La victime clique.
  4. Le site vulnerable renvoie le payload.
  5. Le navigateur de la victime l'execute dans le contexte du site legitime.

Ce qu'il faut retenir:

  1. Le payload ne reste pas dans l'application.
  2. Il voyage dans une seule boucle requete -> reponse.
  3. Sans clic, sans ouverture de lien, il n'y a souvent pas d'exploitation.

Autres lieux frequents:

  1. Page 404 qui reaffiche l'URL demandee.
  2. Message "utilisateur X introuvable".
  3. Filtres de recherche ou de tri affiches dans l'interface.
  4. Pages de debug ou de confirmation.

Exemple 2: Stored XSS

Cas realiste:

Un site permet de publier des commentaires.

Flux vulnerable:

  1. L'attaquant poste un commentaire contenant du HTML/JS.
  2. Le serveur l'enregistre en base.
  3. Plus tard, tout lecteur de la page charge ce commentaire.
  4. 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:

  1. Le payload est persistant.
  2. Une seule soumission peut toucher plusieurs victimes.
  3. Les admins, moderateurs et utilisateurs privilegies consultent souvent ces zones.
  4. Il n'y a pas besoin d'envoyer un lien specifique a chaque victime.

Lieux classiques de Stored XSS:

  1. Commentaires, avis, messages prives, signatures, bios.
  2. Tickets support et interfaces de back-office.
  3. Journaux consultes via une interface web.
  4. Noms de fichiers ou metadonnees affiches dans un panneau d'admin.

Variante importante:

  1. Blind XSS est une forme de Stored XSS.
  2. Le payload est stocke puis execute plus tard dans un back-office que l'attaquant ne voit pas directement.
  3. 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:

  1. Le navigateur lit location.search.
  2. Le code JS traite cette valeur comme du HTML.
  3. innerHTML construit de vrais noeuds DOM a partir de la chaine.
  4. Le payload s'execute sans que le serveur ait besoin de renvoyer ce script dans le HTML initial.

Le point central:

  1. Le serveur peut renvoyer une page "propre".
  2. C'est le JavaScript du client qui rend la page dangereuse.

Autres sources frequentes:

  1. location.hash
  2. document.URL
  3. document.referrer
  4. window.name
  5. postMessage
  6. localStorage et sessionStorage

Sinks dangereux frequents:

  1. element.innerHTML
  2. element.outerHTML
  3. document.write(...)
  4. insertAdjacentHTML(...)
  5. eval(...)
  6. setTimeout("code")
  7. 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 ?":

  1. Reflected XSS
  1. Stored XSS
  1. DOM XSS

Si on pose la question "combien de temps vit le payload ?":

  1. Reflected = une interaction.
  2. Stored = persistant.
  3. 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 ?":

  1. Reflected = souvent oui.
  2. Stored = souvent non.
  3. 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>".

  1. Version Reflected
  1. Version Stored
  1. Version DOM

Le resultat visible peut sembler identique, mais le point de correction n'est pas le meme.


Comment les detecter pendant un test

  1. Pour Reflected XSS
  1. Pour Stored XSS
  1. Pour DOM XSS

Prevention: ce qui change selon le type

  1. Pour Reflected et Stored
  1. Pour DOM XSS
  1. Pour tous les XSS

Les erreurs classiques de raisonnement

  1. "Si c'est en base, c'est safe."
  1. "DOM XSS n'a rien a voir avec reflected/stored."
  1. "Si on filtre <script>, il n'y a plus de XSS."
  1. "Le frontend valide deja."

Resume ultra-court

  1. Reflected XSS
  1. Stored XSS
  1. DOM XSS

References principales

Synthese faite a partir de sources OWASP consultees le 2026-03-13:

  1. OWASP - Cross Site Scripting (XSS)
  1. OWASP - Cross Site Scripting Prevention Cheat Sheet
  1. OWASP - DOM based XSS Prevention Cheat Sheet
  1. OWASP WSTG - Testing for Reflected Cross Site Scripting
  1. OWASP WSTG - Testing for Stored Cross Site Scripting
  1. OWASP WSTG - Testing for DOM-Based Cross Site Scripting