Sommaire
L’utilisation de filtres personnalisés pour protéger une application contre une injection XSS est considérée comme une mauvaise pratique, bien qu’elle soit encore courante. Ces filtres sont généralement insuffisants pour empêcher des entrées utilisateur malveillantes de compromettre la sécurité de l’application.
Cet article présente une étude de cas d’un filtre « simple » rencontré lors d’un test d’intrusion, ainsi que les réflexions permettant de contourner ce filtre et d’exploiter l’injection XSS réfléchie.
Injections XSS et contournement de filtre : lab de la vulnérabilité
Avant de présenter en détail le filtre rencontré et les solutions d’échappements, nous proposons de tester cette XSS réfléchie en téléchargeant le fichier HTML suivant : https://github.com/I-TRACING-ASO/blog-sources/blob/main/XSS-filter-bypass/XSS-challenge.html
Le paramètre username dans l’URL sert de point d’injection pour tester les payloads.
Exploitation de l’injection XSS
Toute la démonstration s’appuiera sur le code fourni dans la section précédente.
Découverte du contexte
Comme pour toutes les injections XSS réfléchies, la première étape de découverte est de regarder dans quel contexte l’entrée utilisateur est injectée, quels sont les caractères spéciaux autorisés, lesquels sont interdits (supprimés) et lesquels sont échappés et par quels moyens.
Contexte d’injection XSS
L’entrée utilisateur est injectée à deux endroits dans la page, comme illustré ci-dessous :

L’analyse du code JavaScript permet de conclure que l’injection dans la balise <span> utilise innerText. Cette méthode échappe automatiquement les caractères HTML, rendant toute injection impossible.
En revanche, le second point d’injection se situe dans une chaîne de caractères d’une balise <script>, dans la définition de la variable username. Sortir de cette chaîne permet d’exécuter du code JavaScript arbitraire.
Traitement de l’entrée utilisateur
Afin de trouver une injection XSS fonctionnelle, il faut s’intéresser au traitement appliqué à l’entrée utilisateur. Ici ce traitement est fait côté client donc la simple lecture du code JavaScript permet de voir ce qu’il se passe. S’il était effectué côté serveur, des tests devraient être faits pour découvrir le traitement de chaque caractère spécial.
function escapeXss(input) {
return input.replace(/"/g, '\\"').replace(/\//g, '\\/');
}
const params = new URLSearchParams(window.location.search);
const usernameParam = params.get('username');
const escapedUsername = usernameParam ? escapeXss(usernameParam) : 'ANONYMOUS';
var script = document.getElementById("user-script")
script.innerText = script.innerText.replace("PLACEHOLDER",escapedUsername)
eval(script.innerText)Deux traitements sont appliqués sur l’entrée utilisateur avant de la réinjecter dans la balise script :
- Les
"sont remplacés par \", évitant l’évasion de la chaîne de caractères. - Les / sont remplacés par \/, bloquant ainsi la création de balises HTML fermantes et les commentaires JavaScript.
Création d’un payload fonctionnel pour l’injection XSS
Evasion de la chaine de caractères
La première étape de création du payload repose sur un contournement de filtre très classique : " est échappé en ajoutant \ avant, mais celui-ci n’est pas échappé. Le payload \" est donc transformée en \\". Le \ ajouté par le filtre est échappé et le " est de nouveau interprété pour fermer la chaine de caractère. En le testant dans le lab on reste bloqué sur un problème de syntaxe JavaScript :

Le \ ajouté avant le " final du payload – qui permet de rouvrir une chaine de caractères pour que le " déjà présent dans le script ne pose pas de problème – n’est pas dans une chaine de caractère et n’est pas correct pour la syntaxe JavaScript. Le code injecté n’est donc pas interprété mais lève une erreur dans la console.
Commentaire du reliquat JavaScript
Une solution pour corriger cette erreur est de commenter la fin de la ligne afin que le " de fermeture de la chaine de caractère ne soit plus interprété, mais les commentaires JavaScript commencent tous par / (que ce soit // ou /* ... */) et posent donc le même problème de syntaxe que le " puisque le filtrage en place les précède d’un \.
L’astuce à utiliser est moins courante que l’échappement du \ utilisé plus tôt : on va commenter la fin de la ligne grâce au parser HTML. En effet les commentaires HTML n’utilisent pas de mais s’ouvrent avec /<!-- et le navigateur les ferment automatiquement à la fin de la balise :

Prévenir le contournement XSS : éviter les filtres personnalisés
L’utilisation de filtres personnalisés pour protéger les applications est inefficace et non recommandée. Les langages et frameworks modernes intègrent des fonctions spécifiques permettant une protection optimale contre les injections. Ces outils devraient être privilégiés afin de garantir une sécurité robuste et de réduire les risques liés aux vulnérabilités XSS ou d’autres injections.
Auteur
Amos GEORGE, Audit & Sécurité Offensive
10 février 2026