Pourquoi les applications web sont exposées, le classement OWASP Top 10, puis l'attaque Cross-Site Scripting en détail - XSS réfléchi, stocké et basé sur le DOM, les contextes d'injection (texte, attribut, script) et les protections (échappement, textContent, Content-Security-Policy, cookies HttpOnly).
1Situer
Un site web accepte du texte venu du monde entier : recherches, avis, pseudos, paramètres d'URL. Chaque fois que ce texte est réaffiché dans une page, il peut devenir du code exécuté par le navigateur des autres visiteurs. C'est le XSS, l'une des failles les plus répandues. Vous l'avez croisé au module 07 ; ici, on en fait le tour complet.
2Comprendre
Le trajet d'un XSS stocké
L'attaquant publie un avis qui contient du code : <script>…</script>.
1 / 5
Pourquoi les applications web sont exposées
Une application web est accessible à tous, tout le temps, et elle reçoit des données que n’importe qui peut fabriquer : paramètres d’URL, champs de formulaire, en-têtes, cookies, fichiers. Chaque endroit où ces données entrent (et ressortent) est une porte potentielle.
Rang 2021
Risque
Leçon
A01
Contrôle d’accès défaillant
B.3, C.2
A02
Défaillances cryptographiques
B.4, C.3
A03
Injections (SQL, XSS, commandes)
A.4, M07 C.5
A05
Mauvaise configuration de sécurité
A.6
A07
Défauts d’identification et d’authentification
A.5, C.1
Le Cross-Site Scripting
Un script injecté peut :
voler un cookie de session qui n’est pas HttpOnly, donc le compte (leçon A.5) ;
agir au nom de la victime (passer une commande, changer l’e-mail du compte) ;
remplacer la page par un faux formulaire de connexion ;
rediriger vers un site piégé.
Trois familles
Type
Où est le code injecté ?
Qui est touché ?
Réfléchi
dans la requête (paramètre d’URL) et renvoyé tel quel dans la réponse
celui qui clique sur le lien piégé
Stocké
enregistré dans la base (avis, pseudo, message)
tous ceux qui affichent la page
DOM
lu et inséré par le JavaScript de la page (innerHTML, document.write)
celui qui ouvre l’URL piégée
Échapper selon le contexte
Une même valeur ne s’échappe pas de la même façon partout :
Contexte
Exemple
Protection
Texte HTML
<p><?= $x ?></p>
htmlspecialchars($x)
Attribut
<input value="<?= $x ?>">
htmlspecialchars($x)et des guillemets autour de l’attribut
JavaScript
const x = <?= ... ?>;
json_encode($x), puis jamais innerHTML avec
URL
<a href="<?= $x ?>">
vérifier que l’adresse commence par https:// (bloquer javascript:)
Les protections en couches
Échapper à la sortie, toujours, avec une petite fonction e() utilisée dans toutes les vues.
Côté JavaScript, préférer textContent, createElement et setAttribute à innerHTML.
Cookies de session en HttpOnly : un script injecté ne peut plus les lire.
Content-Security-Policy : un en-tête qui dit au navigateur quels scripts il a le droit d’exécuter.
La CSP est un filet de sécurité : elle limite les dégâts d’un oubli d’échappement, mais elle oblige aussi à placer son propre JavaScript dans des fichiers .js séparés.
Exemple simple
Le panneau d'affichage de la mosquée
Chacun peut épingler une annonce sur le panneau. Si quelqu'un épingle une fausse annonce « donnez vos clés au porteur de ce papier », tous les lecteurs sont trompés. Un avis client non échappé est cette fausse annonce.
Exemple simple
Le haut-parleur du souk
Le marchand répète au haut-parleur ce que le client lui dit. Si le client dit « tout est gratuit aujourd'hui », c'est la voix du marchand qui l'annonce. Une page qui réaffiche une recherche sans l'échapper fait parler le site à la place de l'attaquant.
Publicité
3Manipuler
La page de recherche contient trois failles XSS (texte, attribut, JavaScript). Essayez les attaques proposées en commentaire, observez le résultat, puis corrigez chaque sortie.
Pas à pas dans le lab ↗Cliquez sur Exécuter : PHP 8.3 démarre dans votre navigateur (environ 5 Mo à télécharger, une seule fois).
La page s'affichera ici.
Le navigateur demandera /recherche.php?q=savon&prenom=Amina au serveur, qui exécutera le PHP et renverra du HTML.
Entre le navigateur et le serveuraucun échange pour l'instant
Consignes 0 / 4
Contexte texte : la balise injectée s'affiche comme du texte
Contexte attribut : le guillemet ne ferme plus l'attribut value
Contexte DOM : le prénom est inséré avec textContent au lieu de innerHTML
La réponse envoie un en-tête Content-Security-Policy
4Pratiquer
Exercice 1 · Réfléchi, stocké ou DOM ?
Classez : (a) un pseudo contenant un script, enregistré dans la base et affiché à tous les visiteurs du forum ; (b) un lien piégé envoyé par e-mail, dont le paramètre ?erreur= est réaffiché par la page ; (c) un script de la page qui lit location.hash et l'insère avec innerHTML.
Voir le corrigé
(a) XSS stocké : le code est enregistré côté serveur et touche tous ceux qui affichent la page.
(b) XSS réfléchi : le code voyage dans la requête et revient immédiatement dans la réponse ; il faut faire cliquer la victime.
(c) XSS basé sur le DOM : tout se passe dans le navigateur, le serveur ne voit même pas la valeur (le fragment # n'est pas envoyé).
Exercice 2 · Une fonction d'échappement courte
Écrivez une fonction e() à utiliser dans toutes les vues : <?= e($valeur) ?>. Elle doit gérer les valeurs null et les nombres.