Le Cross-Site Scripting (XSS) reste l'une des vulnérabilités les plus répandues et dangereuses des applications web. Pendant des années, il a dominé la liste OWASP Top Ten, permettant aux attaquants d'injecter des scripts malveillants dans des pages web consultées par d'autres utilisateurs. Bien que le concept fondamental — faire trop confiance aux entrées utilisateur — soit simple, les stratégies d'atténuation sont complexes et nuancées. En tant que développeurs, nous devons aller au-delà des simples filtres regex et adopter une stratégie de défense en profondeur qui combine une validation stricte des entrées, une mise en forme contextuelle des sorties et les protections offertes par les frameworks modernes.
Comprendre le paysage : Trois types de XSS
Avant de plonger dans la prévention, il est crucial de distinguer les trois variantes principales du XSS, car chacune nécessite une approche défensive légèrement différente :
- XSS réfléchi (Reflected XSS) : Le script malveillant provient de la requête HTTP actuelle. Il est souvent délivré via un lien de hameçonnage (phishing). L'application renvoie l'entrée à l'utilisateur sans la nettoyer correctement.
- XSS stocké (Persistent XSS) : Le script malveillant est stocké de manière permanente sur le serveur cible, par exemple dans une base de données, un champ de commentaire ou un message de forum. Chaque fois qu'un utilisateur récupère ces données, le script s'exécute.
- XSS basé sur le DOM (DOM-based XSS) : La vulnérabilité réside dans le code côté client plutôt que côté serveur. L'attaquant modifie l'environnement DOM dans le navigateur de la victime, provoquant l'exécution inattendue de scripts côté client.
Bien que les mécanismes de livraison diffèrent, la cause racine est toujours la même : l'application ne fait pas la distinction entre le code exécutable et les données de confiance.
La règle d'or : La mise en forme contextuelle des sorties
La défense la plus efficace contre le XSS n'est pas nécessairement le filtrage des entrées, mais l'assurance que toutes les données rendues dans le navigateur sont correctement encodées pour leur contexte spécifique. Les navigateurs interprètent le HTML différemment selon qu'ils se trouvent à l'intérieur d'une balise, d'un attribut, d'un bloc de script ou d'un style CSS. Ne pas tenir compte du contexte est la cause la plus fréquente de réussite des attaques.
Considérez la pratique dangereuse suivante dans un moteur de modèles côté serveur (comme Jinja2 en Python ou EJS en Node) où les données ne sont pas automatiquement échappées :
<!-- CODE VULNÉRABLE -->
<div>
Bonjour, <script>{user_input}</script>!
</div>
Si user_input est alert('XSS'), le HTML résultant devient Bonjour, !, exécutant le script immédiatement. La solution consiste à encoder la sortie afin que les caractères spéciaux comme < et > soient convertis en leurs équivalents entités HTML (< et >) :
<!-- CODE SÉCURISÉ -->
<div>
Bonjour, <%= user_input %>!
</div>
Dans les frameworks modernes comme React, Angular ou Vue, la mise en forme des sorties est souvent le comportement par défaut. Lorsque vous utilisez la syntaxe d'interpolation (par exemple, {userInput} dans React), le framework échappe automatiquement le contenu avant de l'insérer dans le DOM, offrant ainsi une couche de protection significative contre le XSS stocké et réfléchi.
Se défendre contre le XSS basé sur le DOM
Le XSS basé sur le DOM présente un défi unique car la vulnérabilité réside dans l'exécution de JavaScript, et non dans le rendu HTML. Les attaquants exploitent des API DOM non sécurisées qui acceptent des données contrôlées par l'utilisateur sans validation.
Les API dangereuses courantes incluent document.write(), innerHTML et location.hash. Utiliser ces fonctions avec des données non fiables est une recette pour le désastre.
<!-- JAVASCRIPT DOM DANGEREUX -->
<script>
// N'utilisez jamais innerHTML avec des données non nettoyées
document.getElementById('output').innerHTML = location.hash.substring(1);
</script>
Pour prévenir cela, utilisez textContent pour insérer du texte ou des méthodes de manipulation DOM sûres. Si vous devez insérer du HTML, utilisez une bibliothèque de confiance qui nettoie l'entrée avant l'insertion. De plus, la Politique de Sécurité du Contenu (CSP) agit comme un filet de sécurité critique pour le XSS basé sur le DOM. En restreignant les sources à partir desquelles les scripts peuvent être chargés et exécutés, la CSP peut neutraliser de nombreuses charges utiles XSS même si une injection se produit.
La validation des entrées comme couche secondaire
Bien que la mise en forme des sorties soit la défense principale, la validation des entrées sert de couche secondaire précieuse. Cette stratégie, souvent appelée « whitelist », consiste à définir exactement à quoi ressemble une entrée acceptable et à rejeter tout le reste. Par exemple, si un champ attend une adresse e-mail, utilisez une expression régulière qui correspond strictement aux formats d'e-mails valides. S'il attend un âge, assurez-vous que l'entrée est un entier dans une plage raisonnable.
Cependant, ne vous fiez jamais uniquement à la validation des entrées. Le blacklistage de mots-clés spécifiques comme <script> ou onerror= est inefficace car les attaquants peuvent contourner ces filtres en utilisant l'encodage, les changements de casse ou de nouveaux gestionnaires d'événements. La validation doit être utilisée pour appliquer les règles métier, et non comme seule barrière de sécurité contre les injections.
Conclusion
Prévenir le XSS nécessite une approche multifacette. Commencez par supposer que toute entrée utilisateur est malveillante. Profitez des frameworks modernes qui gèrent la mise en forme des sorties par défaut. Implémentez un encodage contextuel lors de la création de solutions personnalisées. Défendez-vous contre les attaques basées sur le DOM en auditant le JavaScript côté client pour détecter les API non sécurisées. Enfin, déployez des en-têtes de Politique de Sécurité du Contenu pour fournir une dernière ligne de défense robuste. En intégrant ces pratiques à votre cycle de développement, vous pouvez réduire considérablement le risque de XSS et construire des applications web plus sécurisées et dignes de confiance.