Aller au contenu
M08 · A. Introduire la sécurité informatique

Les vulnérabilités des applications web et le XSS

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é
Attaquantpublie un avis Site webavis.php Basetable avis Clientenavigateur cookie volé <script>fetch(…+document.cookie) &lt;script&gt;… affiché comme du texte

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 2021RisqueLeçon
A01Contrôle d’accès défaillantB.3, C.2
A02Défaillances cryptographiquesB.4, C.3
A03Injections (SQL, XSS, commandes)A.4, M07 C.5
A05Mauvaise configuration de sécuritéA.6
A07Défauts d’identification et d’authentificationA.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

TypeOù est le code injecté ?Qui est touché ?
Réfléchidans la requête (paramètre d’URL) et renvoyé tel quel dans la réponsecelui qui clique sur le lien piégé
Stockéenregistré dans la base (avis, pseudo, message)tous ceux qui affichent la page
DOMlu 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 :

ContexteExempleProtection
Texte HTML<p><?= $x ?></p>htmlspecialchars($x)
Attribut<input value="<?= $x ?>">htmlspecialchars($x) et des guillemets autour de l’attribut
JavaScriptconst 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

  1. Échapper à la sortie, toujours, avec une petite fonction e() utilisée dans toutes les vues.
  2. Côté JavaScript, préférer textContent, createElement et setAttribute à innerHTML.
  3. Cookies de session en HttpOnly : un script injecté ne peut plus les lire.
  4. Content-Security-Policy : un en-tête qui dit au navigateur quels scripts il a le droit d’exécuter.
header("Content-Security-Policy: default-src 'self'");

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.

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 serveur aucun échange pour l'instant

Consignes 0 / 4

  1. Contexte texte : la balise injectée s'affiche comme du texte
  2. Contexte attribut : le guillemet ne ferme plus l'attribut value
  3. Contexte DOM : le prénom est inséré avec textContent au lieu de innerHTML
  4. 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.

Voir le corrigé
<?php
function e(mixed $v): string
{
    return htmlspecialchars((string) ($v ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>
<p>Bonjour <?= e($_GET['prenom'] ?? null) ?></p>
<input name="ville" value="<?= e($ville) ?>">

Exercice 3 · innerHTML ou textContent

Réécrivez sans risque : liste.innerHTML += '<li>' + avis.texte + '</li>';

Voir le corrigé
const li = document.createElement('li');
li.textContent = avis.texte;   // le texte reste du texte, même s'il contient <script>
liste.append(li);

5Vérifier

1. Où s'exécute le code injecté par une attaque XSS ?
2. Quelle est la protection principale contre le XSS côté PHP ?
3. Que fait l'option HttpOnly d'un cookie ?