XSS DOM (CWE-79) : données attaquant injectées dans un sink DOM dangereux côté client, sans transiter par le serveur — invisible aux scanners DAST classiques.
location.hash, postMessage) vers un sink (innerHTML, eval)new Function() dans le rendu de polices exécutait du JS arbitraireLe XSS basé sur le DOM (CWE-79, Type-0 XSS dans la taxonomie OWASP) est une variante où la vulnérabilité existe entièrement dans le JavaScript côté client. La réponse du serveur est légitime — aucune donnée malveillante ne passe par la couche HTTP. À la place, le code côté client lit des données contrôlées par l'attaquant depuis une source DOM et les écrit dans un sink dangereux sans sanitization. Le script s'exécute dans le navigateur de la victime sous l'origine de l'application.
Cela rend le XSS DOM fondamentalement différent des variantes réfléchie et stockée : les WAF côté serveur ne voient aucun trafic d'attaque, et tout outil DAST qui examine uniquement les réponses HTTP ne le détectera pas. La détection nécessite une analyse JavaScript dynamique — un navigateur sans tête instrumentant l'exécution réelle, traçant les données de la source au sink.
Le flux de données source → sink est le modèle central de l'analyse du XSS DOM.
Sources — entrées contrôlables par l'attaquant que JavaScript peut lire :
| Source | Notes |
|---|---|
location.search | Chaîne de requête URL — la plus courante |
location.hash | Identifiant de fragment — jamais envoyé au serveur, invisible pour les WAF |
document.referrer | Contrôlé par l'attaquant via navigation |
window.name | Persiste à travers les navigations ; contourne la même origine |
Données postMessage | Cross-origin ; ne nécessite pas de validation event.origin |
localStorage / sessionStorage | Données précédemment contaminées |
document.cookie | Injection de cookie via CRLF ou sous-domaine |
| Messages WebSocket | Flux de données en temps réel |
Sinks — opérations d'écriture DOM dangereuses pouvant produire une exécution de script :
| Sink | Danger | Notes |
|---|---|---|
element.innerHTML | Critique | Exécute les gestionnaires d'événements et <script> |
document.write() | Critique | Casse le contexte du parseur |
eval() | Critique | Exécution directe de code |
setTimeout(chaîne, ...) | Critique | L'argument chaîne est évalué comme JS |
setInterval(chaîne, ...) | Critique | Même chose |
Function(chaîne)() | Critique | Chemin rapide de rendu de polices de PDF.js — voir le deep-dive CVE-2024-4367 ci-dessous |
element.insertAdjacentHTML() | Critique | Équivalent à innerHTML |
location.href = "javascript:..." | Élevé | Redirection vers URI JS |
element.setAttribute("onerror", ...) | Élevé | Injection de gestionnaire d'événements |
element.src / element.href | Moyen | URI data: / javascript: |
Le schéma classique de XSS DOM lit depuis location.search et écrit dans innerHTML :
// VULNÉRABLE — de la source au sink sans sanitization
const params = new URLSearchParams(location.search);
const name = params.get('name');
document.getElementById('greeting').innerHTML = 'Bonjour, ' + name;
// Payload : ?name=<img src=x onerror=alert(document.domain)>
// Le navigateur insère le HTML brut, exécute le gestionnaire onerrorLe serveur renvoie une réponse 200 normale. Toute la vulnérabilité existe dans deux lignes de JavaScript côté client.
Variante postMessage — exploitable depuis n'importe quelle page cross-origin :
// VULNÉRABLE — pas de validation d'origine
window.addEventListener('message', function(e) {
// e.origin non vérifié — n'importe quelle origine peut envoyer
document.getElementById('content').innerHTML = e.data; // sink
});// Page de l'attaquant (n'importe quelle origine) :
const target = window.open('https://victim.com/app');
setTimeout(() => {
target.postMessage('<img src=x onerror=alert(document.domain)>', '*');
}, 1000);CVE-2024-49038 (Microsoft Copilot Studio, CVSS 9.3) a exploité un schéma postMessage/injection DOM dans un service AI cloud, permettant une escalade de privilèges cross-tenant.
La prototype pollution définit des propriétés arbitraires sur Object.prototype via des paramètres de requête craftés. Quand le code de l'application ou une bibliothèque lit une propriété indéfinie d'un objet quelconque, elle remonte au prototype pollué.
// Étape 1 : Prototype pollution via URL
// ?__proto__[transport_url]=data:,alert(document.domain);
// Définit : Object.prototype.transport_url = "data:,alert(1)"
// Étape 2 : Le gadget jQuery lit la propriété
$.ajax({ url: '/api', jsonp: callback });
// jQuery lit en interne options.transport_url — indéfini sur l'objet options
// Remonte vers Object.prototype.transport_url → le payload de l'attaquant s'exécuteCVE-2026-41238 démontre que DOMPurify lui-même est un gadget de prototype pollution : polluer Object.prototype.tagNameCheck avec une regex permissive amène DOMPurify à laisser passer des éléments arbitraires dans la sanitization, convertissant une vulnérabilité de prototype pollution n'importe où dans l'application en bypass XSS complet.
Outils de détection : Burp Suite DOM Invader (scanner de prototype pollution intégré), l'extension Chrome PPScan, et le dépôt GitHub client-side-prototype-pollution (liste de gadgets maintenue par BlackFan).
Le DOM clobbering utilise des éléments HTML nommés — injectés via injection HTML dans des contextes où <script> est bloqué — pour masquer des variables JavaScript globales.
<!-- Clobber à un niveau : window.x devient un HTMLElement -->
<a id="x" href="https://attacker.com/malicious.js"></a>
<!-- Clobber à deux niveaux : window.config.cdn devient une chaîne via l'attribut href d'une ancre -->
<a id="config"><a id="config" name="cdn" href="https://attacker.com">Quand le code de l'application exécute var url = window.config.cdn || '/assets/', la valeur clobbered config.cdn est https://attacker.com — contrôlée par l'attaquant.
CVE-2024-7524 — Contournement CSP strict-dynamic de Firefox : Le shim ETP du SDK Facebook lit document.currentScript.src sans valider le tagName. Injecter <img name="currentScript" src="data:,alert(document.domain)"> amène le shim à charger l'URI data: comme script enfant de confiance sous strict-dynamic, contournant entièrement le CSP.
Le DOM clobbering escalade l'injection HTML — où <script> est bloqué — en exécution complète de script. Une application qui sanitise <script> mais autorise les attributs id et name sur les éléments <a> et <form> reste vulnérable aux chaînes de XSS par DOM clobbering.
new Function() est dangereux précisément parce qu'il contourne entièrement l'étape de parsing HTML — aucune injection de balise nécessaire — ce qui le rend invisible aux scanners qui n'accrochent que innerHTML ou document.write. Le chemin rapide de rendu de polices de PDF.js utilisait exactement ce sink pour compiler le tableau FontMatrix d'un PDF en JavaScript exécutable, permettant à une définition de police malveillante d'exécuter du code arbitraire dès l'ouverture du fichier par la victime.
Analyse technique complète — payload d'injection, versions affectées, correctif — dans le deep-dive CVE-2024-4367 ci-dessous.
CVE-2024-4367 (publié le 14 mai 2024 sur NVD ; avis Mozilla MFSA-2024-21, MFSA-2024-22, MFSA-2024-23) est un XSS basé sur le DOM dans PDF.js, le moteur de rendu PDF en JavaScript de Mozilla, intégré à Firefox et distribué en standalone via le package npm pdfjs-dist (~2,7 millions de téléchargements hebdomadaires au moment de la divulgation). Il obtient un score CVSS 3.1 de 8,8 ÉLEVÉ (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H) et est classé CWE-754 (vérification incorrecte d'une condition inhabituelle ou exceptionnelle) — une absence de vérification de type, pas un bug classique d'échappement de chaîne.
PDF.js précompile les instructions de rendu de glyphes en JavaScript pour la performance dès que son option isEvalSupported est activée — c'était le comportement par défaut à l'époque. Le compilateur construit un corps new Function() à partir du tableau FontMatrix de la police, six valeurs que la spécification PDF définit comme des nombres. PartialEvaluator.translateFont() lit ce tableau directement depuis le dictionnaire de police du PDF et le joint dans le code source de la fonction sans vérifier que chaque entrée est bien un nombre.
Comme le format PDF autorise techniquement qu'une entrée FontMatrix soit une chaîne PDF, un attaquant en glisse une :
/FontMatrix [1 2 3 4 5 (0\); alert\('CVE-2024-4367'\))]La chaîne finale referme la liste d'arguments numériques légitime et ajoute du JavaScript arbitraire, que PDF.js compile ensuite tel quel dans le corps de la Function.
FontMatrix malveillant.PartialEvaluator.translateFont() extrait le tableau pendant le parsing de la police — aucune vérification de type sur les entrées individuelles.new Function(...) et l'invoque — le même schéma de sink Function(chaîne)() que dans le tableau ci-dessus, atteint via les métadonnées de police plutôt qu'une URL.resource://pdf.js dans Firefox, ou l'origine de la page hôte pour pdfjs-dist embarqué), avec accès à window.PDFViewerApplication.url et à tout autre objet accessible depuis cette origine.Aucune balise <script> ni étape de parsing HTML n'est impliquée — toute la chaîne vit à l'intérieur d'un appel au constructeur JavaScript Function, ce qui explique pourquoi elle a échappé aux sanitizers PDF et aux scanners XSS DOM centrés sur le HTML.
| Affecté | pdfjs-dist <= 4.1.392 (toutes les versions publiées) ; Firefox < 126 ; Firefox ESR < 115.11 ; Thunderbird < 115.11 |
| Corrigé | pdfjs-dist 4.2.67 (publié le 29 avril 2024) ; Firefox 126 ; Firefox ESR 115.11 ; Thunderbird 115.11 |
| CVSS 3.1 | 8,8 ÉLEVÉ — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
| CWE | CWE-754 — vérification incorrecte d'une condition inhabituelle ou exceptionnelle |
L'exploitation nécessite que la victime ouvre un PDF malveillant (UI:R) mais aucune authentification ni interaction préalable avec le site cible — toute application rendant des PDF non fiables côté client avec une version vulnérable de pdfjs-dist héritait d'une exécution JavaScript complète dans sa propre origine.
pdfjs-dist vers >= 4.2.67 ; Firefox vers >= 126 ; Firefox ESR vers >= 115.11 ; Thunderbird vers >= 115.11. Le correctif supprime le chemin de compilation Function() non validé.// Options du visualiseur PDF.js — désactive le chemin rapide de compilation de glyphes vulnérable
const loadingTask = pdfjsLib.getDocument({
url: pdfUrl,
isEvalSupported: false,
});unsafe-eval dans script-src bloque directement le constructeur Function, fermant toute cette classe de sink même contre des variantes non encore découvertes. C'est le même contrôle qui protège contre les techniques de contournement de sanitizer couvertes dans mXSS — Mutation XSS.CVE-2024-4367 est l'un des incidents à l'origine du basculement plus large vers la surface d'attaque basée sur le DOM — voir la recherche XSS Trends 2025–2026 de BreachVex pour l'état actuel du paysage.
location.search, location.hash, document.referrer, window.name, addEventListener('message', ...)postMessage avec un payload XSS depuis une origine contrôlée par l'attaquant// Test manuel rapide — coller dans la console du navigateur
// Vérifie si location.hash atteint innerHTML
const hash = decodeURIComponent(location.hash.slice(1));
// Vérifier toutes les assignations innerHTML manuellement dans l'onglet Sources des DevToolsBreachVex détecte le XSS DOM en utilisant des sessions de navigateur sans tête instrumentées qui accrochent tous les sinks dangereux au chargement de la page, tracent l'arrivée du canary à chaque sink avec des traces de pile complètes, et confirment l'exécution en interceptant la boîte de dialogue résultante.
Les Trusted Types (appliquées sur YouTube, Microsoft 365 et Stripe) empêchent les sinks DOM d'accepter des chaînes brutes :
// En-tête CSP pour appliquer :
// Content-Security-Policy: require-trusted-types-for 'script'
// Politique sûre — DOMPurify sanitise avant assignation
const policy = trustedTypes.createPolicy('default', {
createHTML: (input) => DOMPurify.sanitize(input),
});
element.innerHTML = policy.createHTML(userInput); // OK — passe par le sanitizer
element.innerHTML = userInput; // TypeError en mode d'applicationCela prévient le XSS DOM même quand un flux source-vers-sink existe — le navigateur lance une exception avant que le sink s'exécute.
Remplacer les sinks dangereux par des équivalents sûrs :
// VULNÉRABLE : innerHTML avec données utilisateur
element.innerHTML = userInput;
// SÛR : textContent pour le texte brut (ne rend jamais le HTML)
element.textContent = userInput;
// SÛR : construction DOM structurée (pas de parsing HTML)
const p = document.createElement('p');
p.textContent = userInput;
container.appendChild(p);
// SÛR : DOMPurify + Trusted Types quand le HTML est nécessaire
element.innerHTML = trustedTypesPolicy.createHTML(DOMPurify.sanitize(userInput));// VULNÉRABLE — pas de vérification d'origine
window.addEventListener('message', function(e) {
element.innerHTML = e.data;
});
// SÛR — liste d'autorisation stricte d'origines attendues
const ALLOWED_ORIGINS = new Set(['https://app.example.com', 'https://checkout.example.com']);
window.addEventListener('message', function(e) {
if (!ALLOWED_ORIGINS.has(e.origin)) return; // rejeter les origines inconnues
element.textContent = e.data; // utiliser un sink sûr
});// Geler Object.prototype pour empêcher la pollution
Object.freeze(Object.prototype);
// Ou : utiliser Object.create(null) pour les objets de configuration (pas de chaîne prototype)
const config = Object.create(null);
config.timeout = 5000;
// La pollution de Object.prototype ne peut pas atteindre les propriétés de configLe XSS basé sur le DOM se produit entièrement dans le JavaScript côté client — aucune donnée malveillante n'atteint le serveur et la réponse HTTP du serveur est propre. Le XSS réfléchi nécessite que le serveur renvoie le payload dans sa réponse. Le XSS DOM est invisible pour les WAF côté serveur et la plupart des outils DAST qui examinent les réponses HTTP.
Les sources sont des entrées contrôlables par l'attaquant lues par JavaScript : location.search, location.hash, document.referrer, window.name, données d'événement postMessage, localStorage, sessionStorage, document.cookie, messages WebSocket et IndexedDB.
Les sinks sont des opérations d'écriture DOM dangereuses : innerHTML, outerHTML, document.write(), eval(), setTimeout(chaîne), setInterval(chaîne), Function(chaîne)(), insertAdjacentHTML(), location.href avec URI javascript:, element.src/href pour les URI data:, et la méthode .html() de jQuery.
La prototype pollution permet à un attaquant de définir des propriétés arbitraires sur Object.prototype via des paramètres de requête comme ?__proto__[key]=val. Quand le code de l'application ou une bibliothèque lit une propriété indéfinie d'un objet, elle remonte au prototype pollué, que l'attaquant a défini comme payload XSS. CVE-2026-41238 montre que DOMPurify lui-même est une cible de gadget.
Le DOM clobbering écrase des variables JavaScript globales avec des éléments HTML nommés. Une injection HTML d'un élément ancre avec l'id 'x' crée window.x comme HTMLElement, masquant toute variable JavaScript nommée x. Cela peut escalader l'injection HTML (où les balises script sont bloquées) en XSS complet en corrompant une variable utilisée comme URL de script ou source innerHTML.
Le XSS DOM nécessite une analyse dynamique : Burp Suite DOM Invader (instrumente Chrome pour tracer le flux de données source vers sink), automatisation de navigateur sans tête avec suivi de contamination (Playwright), ou analyse sémantique CodeQL. Les scanners côté serveur examinant les réponses HTTP ne peuvent pas le détecter.
Oui — les Trusted Types imposent que toutes les assignations aux sinks DOM passent par une politique définie par le développeur. Quand require-trusted-types-for 'script' est dans le CSP, assigner une chaîne brute à innerHTML lance une TypeError. Cela élimine le XSS DOM au niveau de la plateforme pour les applications conformes. Chrome/Edge les appliquent ; le support Firefox est en cours.
CVE-2024-4367 a exploité le pipeline de rendu de polices de PDF.js, qui compilait les instructions de glyphes dans des corps new Function(). Un tableau FontMatrix contrôlable dans les métadonnées PDF injectait du JavaScript arbitraire qui s'exécutait sur le domaine embarqueur. Avec ~2,7 millions de téléchargements npm hebdomadaires, cela affectait toutes les applications web et Electron embarquant PDF.js.