Introduction
Une feuille de calcul sur les XSS basés sur le DOM est votre référence incontournable lorsque vous souhaitez localiser, prévenir et automatiser la protection contre l'injection de scripts côté client. En résumé, il s'agit d'identifier l'endroit où une entrée contrôlée par l'utilisateur (un source) s'écoule dans une API dangereuse (a évier), les remplacer par des modèles sûrs (utiliser texteContenu, créer un élément(ou sanitizers), et intégrez des vérifications dans votre processus de construction, d'exécution et de pentest. Parce que DOM XSS se produit entièrement dans le navigateur et contourne de nombreux filtres traditionnels côté serveur, votre front-end devient la dernière ligne de défense - et l'endroit où la plupart des équipes manquent encore d'automatisation.
Qu'est-ce qu'un XSS basé sur le DOM et pourquoi c'est important ?
Selon PortSwigger, les scripts intersites basés sur le DOM surviennent lorsque "JavaScript prend des données d'une source contrôlable par l'attaquant, telle que l'URL, et les transmet à un puits qui prend en charge l'exécution de code dynamique, tel que eval() ou innerHTML." portswigger.net L'OWASP DOM-based XSS Prevention Cheat Sheet souligne que la principale différence avec les XSS stockés/réfléchis est la suivante injection côté client au moment de l'exécution. cheatsheetseries.owasp.org+1
Dans les applications modernes - Single Page Apps (SPA), utilisation intensive de widgets tiers, construction dynamique du DOM - le risque augmente : les charges utiles peuvent ne jamais atteindre les journaux de votre serveur, les WAF traditionnels peuvent les manquer, et les développeurs ne prennent souvent pas suffisamment en compte les éléments suivants identificateurs de fragments, flux postMessageou nom.de la fenêtreToutes ces sources sont communes. Reconnaître ce changement est la première étape pour mesurer la maturité de votre sécurité.
Cartographie de la menace : sources → puits
Le codage sécurisé commence par une carte mentale de sources (point d'entrée de l'attaquant) et éviers (où l'exécution a lieu). Un blog sur la sécurité résume la situation : "La source est tout endroit d'une page web où l'utilisateur peut ajouter des données... Le puits est l'endroit où vont les données insérées dans la source... et s'il n'est pas assaini, il peut conduire à une vulnérabilité XSS basée sur le DOM". Moyen
Vous trouverez ci-dessous un tableau compact que vous devriez conserver à votre poste de travail et dans vos listes de contrôle :
| Source (point d'entrée) | Cuve (API dangereuse) |
|---|---|
| location.hash, location.search, URLSearchParams | innerHTML, insertAdjacentHTML, outerHTML |
| postMessage, nom.de la fenêtre | eval(), new Function(), setTimeout(string) |
| document.referrer, localStorage, sessionStorage | setAttribute('on...'), element.src = ... |
| Données de widgets tiers non validés | Toute insertion de DOM ou exécution de code implicite |
Comme le souligne l'OWASP, aucune technique unique ne permet d'éviter les XSS : vous devez combiner des puits, un codage, une désinfection et des API sûres. cheatsheetseries.owasp.org
Modèles d'attaque du monde réel avec code et correctifs
Exemple A - Injection d'un sélecteur jQuery via un fragment d'URL
Extrait vulnérable :
js
$(window).on('hashchange', () => {
const target = $(location.hash) ; // contrôlé par l'utilisateur
target[0].scrollIntoView() ;
});
Un attaquant peut fabriquer https://site/page.html#<img src="x" onerror="alert(1)"> - jQuery traite le hachage comme un HTML/sélecteur et la balise en cas d'erreur les déclencheurs.
Fixer :
js
const raw = location.hash.slice(1) ;
const safeId = /^[A-Za-z0-9_-]+$/.test(raw) ? raw : null ;
if (safeId) {
const target = document.getElementById(safeId) ;
if (target) target.scrollIntoView() ;
}
Clé : valider, traiter le hash comme un identifiant et non comme du HTML.
Exemple B - Chaîne postMessage → eval
Extrait vulnérable :
js
window.addEventListener('message', e => {
eval(e.data) ; // dangereux
});
Version corrigée :
window.addEventListener('message', event => {
if (event.origin !== '') return ;
try {
const msg = JSON.parse(event.data) ;
handleMessage(msg) ;
} catch {
console.warn('Format de message invalide') ;
}
});
Éviter evalVérifier l'origine, utiliser une analyse sûre.
Exemple C - XSS sur l'éditeur/la prévisualisation dans un contexte markdown
Vulnérable :
js
preview.innerHTML = marked(userInput) ;
Sécurisé :
js
import DOMPurify from 'dompurify' ;
const dirty = marked(userInput) ;
const clean = DOMPurify.sanitize(dirty) ;
preview.innerHTML = clean ;
Lorsque l'on autorise le HTML généré par l'utilisateur, il est nécessaire de l'assainir.
Mise en œuvre déployable : la rendre opérationnelle
Aide DOM sûre - safe-dom.js
js
import DOMPurify from 'dompurify' ;
export function setSafeHTML(el, dirty) {
const clean = DOMPurify.sanitize(dirty, {
ALLOWED_TAGS : ['b', 'i', 'a', 'p', 'ul', 'li', 'code', 'pre', 'img'],
ALLOWED_ATTR : ['href', 'src', 'alt', 'title', 'rel'],
FORBID_ATTR : ['onerror', 'onclick', 'style']
});
el.innerHTML = clean ;
}
export function setText(el, text) {
el.textContent = String(text ? ? '') ;
}
export function safeSetAttribute(el, name, val) {
if (/^on/i.test(name)) throw new Error('Event handler attribute not allowed') ;
el.setAttribute(name, String(val)) ;
}
Cette bibliothèque permet de centraliser les opérations de sécurité dans les DOM et de réduire les erreurs humaines.
Application statique - échantillon ESLint
js
// .eslintrc.js
règles : {
'no-restricted-syntax' : [
'erreur',
{ selector : "AssignmentExpression[left.property.name='innerHTML']", message : "Utilisez safe-dom.setSafeHTML ou textContent." },
{ selector : "CallExpression[callee.name='eval']", message : "Avoid eval()" },
{ selector : "CallExpression[callee.property.name='write']", message : "Éviter document.write()" }
]
}
Combiner avec des crochets de pré-commission (musqué, lint-staged) pour bloquer les modèles dangereux.
Test CI / Puppeteer - GitHub Actions
.github/workflows/dom-xss.yml déclenche un test du marionnettiste :
// tests/puppeteer/dom-xss-test.js
js
const puppeteer = require('puppeteer') ;
(async ()=>{
const browser = await puppeteer.launch() ;
const page = await browser.newPage() ;
const logs = [] ;
page.on('console', msg => logs.push(msg.text())) ;
await page.goto(${URL}/page.html#<img src="x" onerror="console.log("XSS")">);
attend page.waitForTimeout(1000) ;
if (logs.some(l=>l.includes('XSS'))) process.exit(2) ;
attend le navigateur.close() ;
})();
Échec de la construction en cas de détection.
Surveillance de l'exécution - MutationObserver
js
(function(){
const obs = new MutationObserver(muts=>{
muts.forEach(m=>{
m.addedNodes.forEach(n=>{
if(n.nodeType===1){
const html = n.innerHTML || '' ;
if(/on(error|click|load)|<script\b/i.test(html)){
navigator.sendBeacon('/_monitoring/xss', JSON.stringify({
url : location.href, snippet : html.slice(0,200)
}));
}
}
});
});
});
obs.observe(document.documentElement, {childList:true, subtree:true}) ;
})();
Utile dans la mise en scène pour alerter les injections DOM inattendues.
Renforcement de la sécurité des navigateurs - CSP et Trusted Types
En-tête CSP :
pgsql
Content-Security-Policy : default-src 'self' ; script-src 'self' 'nonce-XYZ' ; object-src 'none' ; base-uri 'self' ;
Extrait des types de confiance :
js
window.trustedTypes ?.createPolicy('safePolicy', {
createHTML : s => { throw new Error('Direct HTML assignment blocked') ; },
createScript : s => { throw new Error('Création directe de script bloquée') ; }
});
Les puits non fiables sont ainsi refusés par défaut.
Sécurité des scripts des tiers - SRI
bash
openssl dgst -sha384 -binary vendor.js | openssl base64 -A
Utilisation <script src="vendor.js" integrity="sha384‑..." crossorigin="anonymous"></script> à épingler et à vérifier.
Intégration avec Penligent pour l'automatisation
Si votre équipe utilise Penligent, vous pouvez élever votre protection DOM XSS à un pipeline continu. L'article de recherche de Penligent indique comment "détecter les XSS basés sur le DOM via le suivi des erreurs d'exécution ... les techniques côté serveur ne permettent pas de détecter de manière fiable les injections côté client". Penligent
Exemple de flux de travail :
- Dans CI, déclencher un scan Penligent avec le jeu de règles
dom-xssen fournissant des données utiles telles que#<img src="x" onerror="alert(1)">. - Penligent exécute des flux sans tête, génère des PoC, renvoie les résultats via un webhook.
- L'IC analyse les résultats : si la gravité est ≥ élevée, la construction échoue et le PR est annoté avec la charge utile + le puits + la recommandation de correction (par exemple, "remplacez
innerHTMLavecsafe-dom.setSafeHTML). - Les développeurs corrigent, relancent l'IC, ne fusionnent que lorsque c'est vert.
La boucle est ainsi bouclée : de la référence (cet aide-mémoire) → politique de codage → détection automatisée → remédiation organisée.
Conclusion
Le front-end n'est plus une simple interface utilisateur. Il est une surface d'attaque. Cet aide-mémoire vous aide à comprendre l'injection côté client, à remplacer les puits dangereux, à construire des bibliothèques d'aide sûres, à déployer une détection statique/CI/runtime et à automatiser avec une plateforme comme Penligent. Prochaines étapes : analyser votre base de code pour détecter les sources d'injection interdites (innerHTML, eval), adopter un sécurité-dom d'appliquer les règles de lint, d'intégrer des tests sans tête et une véritable logique de pentest, de surveiller la production/stage, et d'épingler les ressources tierces. Protéger DOM XSS, c'est faire en sorte que impossible de se faufiler - et non de s'en remettre à la chance.

