Le XSS réfléchi (Cross-Site Scripting)
Jusqu’ici, nous avons regardé des failles où le serveur est la victime directe. Dans ce chapitre, la cible est l’utilisateur : le visiteur d’un site légitime dont le navigateur exécute, à son insu, du code qui n’a pas été écrit par le site. Cette famille de failles est celle du Cross-Site Scripting, abrégé XSS. Commençons par la plus simple à comprendre : le XSS réfléchi.
Rappel légal
Piéger une personne pour que son navigateur exécute du code qu’elle n’a pas voulu exécuter, c’est s’attaquer à une victime réelle. Sur un site tiers, c’est une infraction (articles 323-1 à 323-3-1 du Code pénal), même si vous ne « faites que » afficher un message. Vos exercices se font exclusivement sur DVWA, Juice Shop, la PortSwigger Web Security Academy ou votre laboratoire, avec vos propres comptes de test.
Rappel : comment un navigateur affiche une page
Un navigateur reçoit du HTML et l’interprète. Si ce HTML contient une balise de script, le navigateur exécute le code JavaScript qu’elle contient, avec les droits du site d’origine : accès au contenu de la page, aux actions de l’utilisateur sur ce site, et (sauf protection) aux cookies. C’est ce principe de confiance, la « même origine », qu’une faille XSS fait voler en éclats.
Le principe du XSS réfléchi
Dans un XSS réfléchi (reflected), l’application prend une valeur de la requête (paramètre d’URL, champ de formulaire) et la renvoie immédiatement dans la page de réponse sans la neutraliser. La valeur n’est pas stockée : elle « rebondit ». Pour qu’un visiteur soit touché, il doit être amené à ouvrir une adresse précise, par exemple en cliquant sur un lien reçu par message.
Voici un code maladroit, en pseudo-PHP, pour une page de recherche :
$terme = $_GET['q'];
echo "<p>Résultats pour : " . $terme . "</p>";
Si q vaut chaussures, la page affiche « Résultats pour : chaussures ». Mais si q contient du HTML, le navigateur l’interprétera comme du HTML, pas comme du texte. L’application a réfléchi une donnée non fiable dans un contexte où elle devient du code.
Une analogie : un panneau d’affichage municipal qui reproduirait telle quelle toute pancarte qu’on lui tend, y compris celles qui contiennent de fausses instructions officielles. Les passants feraient confiance au panneau, puisqu’il est municipal.
Ce que ce type de faille permet
Selon la page et les protections en place, un script exécuté dans le contexte du site peut, en principe :
- lire ou modifier le contenu affiché (fausse page de connexion intégrée au vrai site) ;
- effectuer des actions au nom de l’utilisateur connecté ;
- voler des informations accessibles au navigateur, comme un cookie de session non protégé (d’où l’intérêt de l’attribut
HttpOnlyvu au chapitre 2) ; - rediriger vers un autre site.
L’impact dépend du contexte. C’est pourquoi un XSS sur une page d’administration est plus grave qu’un XSS sur une page publique sans compte.
Détecter un XSS réfléchi, avec mesure
Le raisonnement est le même que pour les injections, adapté au contexte HTML :
- Repérer les points de réflexion : votre carte des paramètres contient déjà les endroits où l’application renvoie la saisie (recherche, messages d’erreur, confirmation).
- Insérer une valeur repérable et inoffensive, par exemple un mot unique inventé, et vérifier où il réapparaît dans la page (visualisez le code source).
- Identifier le contexte de réflexion : dans un texte HTML, dans la valeur d’un attribut, dans un bloc de script, dans un lien ?
- Tester si les caractères spéciaux sont neutralisés : envoyez une valeur contenant des chevrons et des guillemets, et regardez dans le code source s’ils ont été transformés (par exemple
<pour le chevron) ou s’ils sont restés tels quels. - Prouver l’exécution avec un test inoffensif : la preuve classique consiste à faire afficher une simple boîte de message dans le navigateur.
Voici ce que l’on observe, dans le code source de la page, selon que la protection existe ou non :
| Entrée | Code source de la réponse | Interprétation |
|---|---|---|
| Texte contenant un chevron ouvrant | Le chevron est devenu < | Neutralisé : le navigateur l’affiche comme du texte |
| Le même texte | Le chevron est resté < | Non neutralisé : le navigateur peut l’interpréter comme une balise |
La preuve se limite à un message d’alerte déclenché par un script de test, dans votre laboratoire. Un auditeur n’a pas besoin d’en faire plus : il établit que du code arbitraire peut s’exécuter, et ce fait suffit pour classer la faille.
<script>alert('test-labo')</script>
Cette valeur, dans le champ de recherche de DVWA au niveau de sécurité bas, fait apparaître une boîte de message : elle ne sert qu’à démontrer l’exécution. Elle ne vole rien et ne contient aucune action. Aux niveaux supérieurs de DVWA, vous constaterez que ce test échoue, ce qui vous renseigne sur les contre-mesures appliquées.
Les contextes de réflexion
La manière d’encoder dépend de l’endroit où la valeur est insérée. C’est une source d’erreurs fréquente :
| Contexte | Danger | Protection adaptée |
|---|---|---|
| Texte dans le corps de la page | Balises HTML interprétées | Encodage HTML des caractères spéciaux |
| Valeur d’un attribut | Sortie de l’attribut pour ajouter un événement | Encodage d’attribut, valeur toujours entre guillemets |
| Dans un bloc de script | Sortie de la chaîne et exécution | Éviter d’insérer des données dans un script ; sinon encodage spécifique |
| Dans une adresse (lien) | Pseudo-protocole de script | Valider le schéma de l’adresse (liste blanche : http, https) |
Les défenses à recommander
- Encoder en sortie : avant d’afficher une donnée dans une page, convertir les caractères spéciaux selon le contexte. C’est la défense principale.
- Utiliser un moteur de modèles qui encode par défaut, et se méfier des fonctions qui désactivent cet encodage.
- Valider les entrées par liste blanche quand le format est connu (un code postal, un identifiant).
- Politique de sécurité du contenu (CSP) : un en-tête qui restreint les sources de scripts autorisés (voir la leçon 4).
- Cookies
HttpOnly: pour limiter les dégâts en cas de XSS.
Voici la correction du code précédent, en pseudo-PHP :
$terme = $_GET['q'] ?? '';
echo "<p>Résultats pour : " . htmlspecialchars($terme, ENT_QUOTES, 'UTF-8') . "</p>";
La fonction transforme les chevrons, les guillemets et l’esperluette en entités. Le navigateur affiche alors la saisie comme du texte brut. Pour approfondir, consultez les aide-mémoire de prévention du XSS sur owasp.org.
Astuce
Le code source de la page (clic droit, afficher le code source) est votre meilleur ami : il montre l’état réel du HTML reçu, avant que le navigateur ne le « range ». L’inspecteur d’éléments, lui, montre la version nettoyée et reconstruite.
À vous de jouer
Dans DVWA (module XSS réfléchi), au niveau de sécurité bas :
- Saisissez un mot unique inventé et retrouvez-le dans le code source de la page : dans quel contexte apparaît-il ?
- Saisissez une valeur contenant des chevrons et regardez si l’application les neutralise.
- Réalisez le test de preuve avec la boîte de message, puis consignez le constat.
- Passez au niveau moyen puis élevé, et décrivez en une phrase comment l’application se défend.
- Lisez le code du niveau « impossible » et repérez la fonction d’encodage.
Voir une solution
Constat type : « XSS réfléchi sur le paramètre name du module de DVWA. La valeur est insérée sans encodage dans la page ; un script de test (boîte de message) s’exécute. Impact : exécution de code dans le navigateur d’un visiteur qui suivrait un lien piégé ; vol de session possible si le cookie n’est pas HttpOnly. Gravité moyenne à élevée selon le contexte. Correction : encodage HTML en sortie, CSP, cookie HttpOnly. » Au niveau moyen, une suppression partielle de balises est contournable ; au niveau élevé, un filtre plus strict ; au niveau « impossible », l’encodage de sortie est appliqué de façon systématique.
À retenir
- Le XSS réfléchi renvoie immédiatement une valeur de la requête dans la page, sans neutralisation.
- Le navigateur exécute alors du code avec les droits du site légitime.
- On teste avec une valeur repérable, l’observation du code source, puis une preuve inoffensive en labo.
- Défense principale : encoder en sortie selon le contexte, complétée par la CSP et les cookies HttpOnly.
- Seule la victime visée change : c’est un utilisateur, pas le serveur.
Dans la leçon suivante, nous verrons des variantes plus insidieuses : le XSS stocké et le XSS basé sur le DOM.