Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
JavaScript

La sécurité côté client : innerHTML, XSS et données non fiables

Apprenez à protéger vos pages contre les failles XSS (Cross-Site Scripting) : notion de donnée non fiable, XSS stockée, réfléchie et basée sur le DOM, dangers de innerHTML, affichage sûr avec textCont...

0 chapitres 0 QCM Accès gratuit
Dans cet article

À propos de ce cours

Apprenez à protéger vos pages contre les failles XSS (Cross-Site Scripting) : notion de donnée non fiable, XSS stockée, réfléchie et basée sur le DOM, dangers de innerHTML, affichage sûr avec textContent et createElement, vérification des URL, nettoyage avec DOMPurify, pièges de eval et des secrets dans le code, intégrité des scripts (SRI) et Content Security Policy. Avec une liste de contrôle et des activités corrigées.
  1. Objectifs

    • Comprendre ce qu'est une faille XSS (Cross-Site Scripting) et ses conséquences.
    • Savoir pourquoi innerHTML est dangereux avec des données non fiables.
    • Afficher des données en toute sécurité avec textContent, createElement et des URL vérifiées.
    • Connaître les protections complémentaires : nettoyage (DOMPurify), CSP, SRI, et la règle « ne jamais faire confiance au client ».
  2. Qu'est-ce qu'une donnée non fiable ?

    • Est non fiable toute donnée qui ne vient pas de votre propre code : saisie d'un formulaire, paramètre de l'adresse (?recherche=...), partie après le #, réponse d'une API, contenu du localStorage, nom de fichier envoyé, message d'un autre utilisateur…
    • Règle d'or : toute donnée extérieure peut contenir du code malveillant. On ne l'insère jamais dans la page comme du HTML sans précaution.
  3. La faille XSS

    • Une faille XSS permet à un attaquant de faire exécuter son JavaScript dans la page d'un autre utilisateur. Ce script peut alors :
      • lire les données de la page et du localStorage (jetons de connexion, informations personnelles) ;
      • agir à la place de l'utilisateur (envoyer un message, modifier un mot de passe) ;
      • afficher un faux formulaire de connexion pour voler des identifiants ;
      • rediriger vers un site piégé.
    • Les trois formes de XSS :
    TypePrincipeExemple
    StockéeLe code malveillant est enregistré sur le serveur puis affiché à tous les visiteurs.Un commentaire de livre d'or contenant une balise piégée.
    RéfléchieLe code est dans un lien envoyé à la victime et renvoyé par le serveur dans la page.site.com/recherche?q=<img src=x onerror=...>
    Basée sur le DOMC'est le JavaScript de la page lui-même qui insère la donnée dangereuse.element.innerHTML = location.hash

    Faille XSS : innerHTML exécute le code injecté, textContent l'affiche comme du texte

  4. Pourquoi innerHTML est dangereux

    • innerHTML demande au navigateur d'interpréter la chaîne comme du HTML. Si la chaîne vient d'un utilisateur, il peut y glisser des balises et des attributs d'événement :
    // Message saisi par un « visiteur »
    const commentaire = '<img src="x" onerror="alert(\'Votre session est volée\')">';
    
    // ✘ DANGEREUX : l'image n'existe pas, onerror s'exécute
    document.querySelector("#commentaires").innerHTML += `<p>${commentaire}</p>`;
    • À noter : une balise <script> insérée avec innerHTML ne s'exécute pas. Cela ne protège de rien : les attributs comme onerror, onload ou onmouseover, eux, s'exécutent.
    • Les mêmes risques existent avec outerHTML, insertAdjacentHTML(), document.write() et les fonctions de jQuery comme .html() ou $("<div>" + saisie + "</div>").
  5. Afficher des données en toute sécurité

  6. 1. textContent pour du texte

    // ✔ SÛR : la chaîne est affichée telle quelle, rien n'est interprété
    const paragraphe = document.createElement("p");
    paragraphe.textContent = commentaire;
    document.querySelector("#commentaires").append(paragraphe);

    2. Construire les éléments au lieu d'assembler du HTML

    function creerCarteCommentaire(auteur, message) {
      const carte = document.createElement("div");
      carte.className = "card mb-2";
    
      const corps = document.createElement("div");
      corps.className = "card-body";
    
      const titre = document.createElement("h5");
      titre.className = "card-title";
      titre.textContent = auteur;      // donnée utilisateur → textContent
    
      const texte = document.createElement("p");
      texte.className = "card-text";
      texte.textContent = message;     // donnée utilisateur → textContent
    
      corps.append(titre, texte);
      carte.append(corps);
      return carte;
    }

    3. Vérifier les adresses (liens et images)

  • Un lien dont l'adresse vient d'un utilisateur peut contenir javascript:...: le code s'exécute au clic. On n'accepte que http: et https::
function creerLienSur(adresse, texte) {
  const lien = document.createElement("a");
  lien.textContent = texte;
  try {
    const url = new URL(adresse, location.href);
    if (url.protocol === "http:" || url.protocol === "https:") {
      lien.href = url.href;
    }
  } catch {
    // adresse invalide : on ne met pas de lien
  }
  return lien;
}

4. Quand on a vraiment besoin de HTML : le nettoyer

  • Pour afficher du HTML venant d'un éditeur de texte riche, on utilise une bibliothèque de nettoyage reconnue comme DOMPurify, qui retire scripts et attributs dangereux :
// après avoir chargé DOMPurify (npm install dompurify ou CDN)
zone.innerHTML = DOMPurify.sanitize(htmlRecu);
  • N'écrivez jamais votre propre filtre avec des expressions régulières : il existe d'innombrables façons de le contourner.
  1. Autres pièges à éviter

    • eval(), new Function() et setTimeout("code", ...) avec une chaîne : ils exécutent du texte comme du code. On passe toujours une fonction: setTimeout(() => afficher(), 1000).
    • Données secrètes dans le code: tout le JavaScript est visible (Ctrl + U, DevTools). Jamais de mot de passe, de clé d'API privée ou de clé secrète dans un fichier .js.
    • Jetons de connexion dans le localStorage: un script injecté peut les lire. Les cookies HttpOnly définis par le serveur sont inaccessibles à JavaScript.
    • Validation uniquement côté client: elle améliore le confort, mais un attaquant peut l'ignorer (DevTools, requête directe). Le serveur doit toujours revérifier.
    • Scripts externes: un script chargé depuis un CDN a tous les droits sur votre page. L'attribut integrity (SRI) vérifie que le fichier n'a pas été modifié :
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.9/dist/js/bootstrap.bundle.min.js"
            integrity="sha384-…empreinte fournie par le site de Bootstrap…"
            crossorigin="anonymous" defer></script>
  2. La Content Security Policy (CSP)

    • La CSP est une règle envoyée par le serveur (en-tête HTTP) qui indique au navigateur quels scripts il a le droit d'exécuter. Même si une faille XSS existe, une CSP stricte bloque le code injecté.
    • Exemple d'en-tête : autoriser seulement les scripts du site et de jsDelivr, interdire les scripts dans les attributs :
    Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; object-src 'none'
    • Avec cette règle, les attributs onclick et onerror et les scripts internes sont bloqués : une raison de plus d'écrire tout son JavaScript dans des fichiers externes avec addEventListener.
    • On peut aussi la tester dans une page avec une balise <meta http-equiv="Content-Security-Policy" content="...">.
  3. Liste de contrôle sécurité

  4. QuestionBonne pratique
    J'affiche une donnée extérieure ?textContent ou createElement
    J'ai besoin de HTML venant de l'extérieur ?DOMPurify.sanitize()
    J'utilise une adresse fournie ?Vérifier que le protocole est http: ou https:
    J'exécute du texte comme du code ?Jamais : pas de eval, pas de setTimeout avec une chaîne
    Je valide un formulaire ?Côté client pour le confort, côté serveur pour la sécurité
    Je charge un script externe ?Source de confiance, version fixée, attribut integrity
    J'ai un secret dans mon code ?Le déplacer côté serveur
  5. Activités

  6. Activité 1 : observer une faille

  • Créez une page avec un champ et un bouton « Publier » qui ajoute le message dans une liste avec innerHTML.
  • Publiez Bonjour <b>à tous</b>, puis <img src="x" onerror="document.body.style.background='red'">. Décrivez ce qui se passe.
  • Corrigez avec textContent et republiez les deux messages. Que voit-on maintenant ?

Activité 2 : trouver les lignes dangereuses

const params = new URLSearchParams(location.search);
const nom = params.get("nom");
document.querySelector("#titre").innerHTML = "Bonjour " + nom;
document.querySelector("#pseudo").textContent = nom;
document.querySelector("#site").href = params.get("site");
setTimeout("verifierSession()", 5000);
const cleApi = "sk_live_51H8…";
  • Pour chaque ligne, indiquez si elle est sûre ou dangereuse, expliquez pourquoi et proposez une correction.

Activité 3 : une carte de commentaire sûre

  • Utilisez la fonction creerCarteCommentaire de la leçon pour afficher un tableau de 3 commentaires, dont un contient une balise piégée. Vérifiez dans l'inspecteur que la balise apparaît comme du texte.

Activité 4 : tester une CSP

  • Ajoutez à la page de l'activité 1 (version non corrigée) la balise <meta http-equiv="Content-Security-Policy" content="script-src 'self'">, avec votre code dans un fichier externe. Publiez de nouveau le message piégé : que signale la console ?
  • Déposez les trois versions dans le dépôt GitHub js-securite-xss.
Corrigé de l'activité 2. Ligne 3 : dangereuse (XSS basée sur le DOM avec ?nom=<img src=x onerror=...>) → utiliser textContent. Ligne 4 : sûre. Ligne 5 : dangereuse (?site=javascript:...) → vérifier le protocole avec new URL(). Ligne 6 : dangereuse (code sous forme de chaîne) → setTimeout(verifierSession, 5000). Ligne 7 : dangereuse (clé secrète visible par tous) → la garder sur le serveur.
Activité 4 : la console affiche un message de violation de la CSP (Executing inline event handler violates the following Content Security Policy directive…) et le code de onerror n'est pas exécuté.
  • À retenir

    • Toute donnée extérieure est non fiable.
    • innerHTML + donnée non fiable = faille XSS; on utilise textContent et createElement.
    • On vérifie les URL, on bannit eval, on ne met aucun secret dans le JavaScript.
    • La sécurité réelle se fait aussi côté serveur; la CSP ajoute une protection supplémentaire.

    Sources : MDN Web Docs (developer.mozilla.org), OWASP (owasp.org), documentations d'ESLint, Prettier et Vitest.