Aller au contenu
Cybersécurité

Quand les filtres personnalisés échouent : étude de cas d’une injection XSS par contournement de filtre

Dans cette étude de cas, nos experts présentent comment contourner les filtres personnalisés avec un simple contournement de filtre afin de réaliser une injection XSS.

Partager sur
Cyber Threat abstract visual, with light grey background, blue circles and green or red lines between the circles

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.

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.

Toute la démonstration s’appuiera sur le code fourni dans la section précédente.

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.

L’entrée utilisateur est injectée à deux endroits dans la page, comme illustré ci-dessous :

Capture d'écran du code JavaScript d'un filtre personnalisé dans le cadre d'une injection XSS.

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.

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.

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

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 :

Création d'un payload fonctionnel pour l'injection XSS en échappant les guillemets dans le code JavaScript du filtre personnalisé.

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.

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 :

Commentaire HTML du code JavaScript d'un filtre personnalisé dans le cadre d'une injection XSS.

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.

Amos GEORGE, Audit & Sécurité Offensive

10 février 2026