JavaScript côté client lit location.href, location.hash ou des paramètres URL et les assigne directement à window.location ou document.location.
TL;DR
window.locationwindow.location, document.location.href, location.replace(), location.assign()location.search, location.hash (jamais loguée), document.referrer, postMessage, window.namenew URL() pour extraire le hostname avant toute assignation de navigationLa redirection ouverte DOM-based est une variante côté client de CWE-601 où la logique de redirection s'exécute entièrement dans le navigateur, pilotée par JavaScript lisant des données contrôlées par l'utilisateur depuis le DOM. Contrairement à la redirection ouverte côté serveur — qui émet une réponse HTTP 30x — la redirection DOM-based ne produit aucun changement de réponse HTTP observable. Le navigateur navigue vers l'URL contrôlée par l'attaquant directement depuis JavaScript sans aller-retour serveur.
Cette variante relève d'OWASP A01:2021 (Broken Access Control) et suit des patterns précurseurs de XSS DOM en termes de flux de données : source → propagation de taint → sink. Les scanners de vulnérabilités automatisés qui n'exécutent pas JavaScript manqueront systématiquement cette classe — la couverture nécessite une instrumentation de navigateur headless ou une revue manuelle du code côté client.
La variante hash est particulièrement dangereuse car le fragment URL n'est jamais envoyé au serveur — il existe uniquement dans le navigateur et dans les logs côté client.
// VULNÉRABLE — redirection DOM basée sur le hash
// URL: https://app-de-confiance.com/dashboard#https://evil.com/capture
const hashValue = window.location.hash.slice(1); // "https://evil.com/capture"
if (hashValue) {
window.location = hashValue; // Redirection ouverte — pas de validation
}// VULNÉRABLE — redirection DOM par paramètre de requête
const params = new URLSearchParams(window.location.search);
const next = params.get("next");
if (next) {
window.location.href = next; // Pas de vérification d'hôte — navigation directe
}// VULNÉRABLE — redirection basée sur postMessage
window.addEventListener("message", (event) => {
// Manquant : if (event.origin !== "https://origine-de-confiance.com") return;
const data = JSON.parse(event.data);
if (data.redirect) {
window.location.replace(data.redirect); // L'attaquant envoie {redirect: "https://evil.com"}
}
});| Source | Sink | Notes de Bypass | Détectabilité |
|---|---|---|---|
location.search (?next=) | window.location | Mêmes que les bypasses côté serveur | Facile (scanner) |
location.hash (#url) | location.href | Fragment jamais envoyé au serveur — invisible dans logs | Difficile |
document.referrer | location.replace() | L'attaquant contrôle Referer en naviguant depuis sa page | Moyen |
window.name | location.assign() | Persiste entre navigations — défini sur la page attaquant | Difficile |
Données postMessage | window.location | Nécessite wildcard * ou vérification d'origine incorrecte | Moyen |
localStorage | document.location | Nécessite un XSS sur sous-domaine préalable | Difficile |
webpack-dev-server incluait une overlay d'erreur de développement qui lisait des destinations de redirection depuis les paramètres URL sans validation. Un attaquant pouvait créer une URL vers le serveur de développement déclenchant une redirection DOM-based vers une page de phishing — ciblant des développeurs avec accès aux secrets de production.
CVE-2024-43788 — webpack-dev-server (CVSS 6.1) L'overlay d'erreur de développement de webpack-dev-server contenait une redirection ouverte DOM-based via des paramètres URL non validés. Tout environnement de développement servant webpack-dev-server était vulnérable aux attaques de phishing ciblant les développeurs. Corrigé dans webpack-dev-server 5.1.0 en validant les destinations de redirection contre une allowlist.
Pattern de Redirection par Fragment Hash (Répandu)
Les applications single-page utilisant location.hash pour la redirection post-authentification (/login#/destination-souhaitée) contiennent souvent des redirections ouvertes DOM-based quand le hash n'est pas validé avant d'être passé au routeur ou window.location. Les applications React et Vue qui gèrent les redirections externes en vérifiant if (next.startsWith("/")) sont typiquement vulnérables au bypass //evil.com — puisque //evil.com commence par /, la vérification passe.
Redirection postMessage dans Widgets Embarqués (Multiples Reports HackerOne)
Les widgets de chat embarqués, iframes de paiement et intégrations tierces utilisent fréquemment postMessage pour la communication cross-frame. Quand le gestionnaire de message navigue selon les données du message sans validation d'origine, n'importe quelle page embarquant le widget peut déclencher une redirection sur la page parente. Bounties dans la fourchette 500-2 000 $ pour ce pattern sur des plateformes majeures.
window.location, document.location, location.href, location.hash, location.replace, location.assign, window.open.https://target.com/page#CANARY_REDIRECT et vérifier si le navigateur navigue ou si window.location.hash est assigné à un sink de navigation.# Burp Suite DOM Invader — injecter une chaîne canary, tracer vers les sinks de navigation
# Activer DOM Invader via le navigateur Burp, naviguer l'application
# semgrep — analyse statique des sinks de navigation JavaScript
semgrep --config=auto --lang=javascript \
--pattern='window.location = $VAR' \
--pattern='location.href = $VAR' \
src/
# dalfox v3+ avec mode DOM redirect
dalfox url "https://target.com/page" --open-redirect --mining-domBreachVex détecte les redirections DOM-based en exécutant le JavaScript dans un navigateur headless instrumenté et en surveillant les événements de navigation. Un finding confirmé nécessite que le navigateur navigue réellement vers l'URL canary, pas seulement une correspondance de chaîne dans le code source.
// BONNE PRATIQUE — valider avec constructeur WHATWG URL avant toute navigation
const ALLOWED_HOSTS = new Set(["app.example.com", "dashboard.example.com"]);
function safeNavigate(url) {
if (!url) return;
// Autoriser les chemins relatifs (préfixe slash unique, pas protocol-relative)
if (url.startsWith("/") && !url.startsWith("//")) {
window.location.href = url;
return;
}
try {
const parsed = new URL(url); // WHATWG — même parseur que le navigateur
if (ALLOWED_HOSTS.has(parsed.hostname)) {
window.location.href = url;
} else {
window.location.href = "/dashboard"; // Fallback sûr
}
} catch {
window.location.href = "/dashboard"; // URL invalide — fallback
}
}
// Usage — remplace window.location = next
const next = new URLSearchParams(window.location.search).get("next");
safeNavigate(next);// BONNE PRATIQUE — utiliser le hash uniquement pour le routage SPA same-origin
const hash = window.location.hash.slice(1);
if (hash && hash.startsWith("/")) {
// Route SPA interne — sûr
router.push(hash);
} else if (hash) {
// URL externe dans le hash — rejeter
console.warn("Redirection externe par hash bloquée:", hash);
}// MAUVAISE PRATIQUE — accepte des messages de n'importe quelle origine
window.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
window.location.href = data.redirect; // Vulnérable
});
// BONNE PRATIQUE — valider l'origine avant traitement
const TRUSTED_ORIGINS = new Set(["https://partner.example.com"]);
window.addEventListener("message", (event) => {
if (!TRUSTED_ORIGINS.has(event.origin)) {
return; // Ignorer les messages d'origines non fiables
}
const data = JSON.parse(event.data);
if (data.redirect) {
safeNavigate(data.redirect); // Utiliser la fonction safeNavigate validée
}
});Le hook useNavigate() de React Router v6 empêche la navigation externe par design — il ne gère que les routes same-origin. Cependant, le code utilisant window.location.href directement pour les redirections externes contourne complètement les garanties de React Router. Recherchez les assignations directes window.location dans votre codebase React.
Une redirection ouverte DOM-based se produit entièrement dans le navigateur : JavaScript lit une URL depuis une source contrôlable (window.location.search, location.hash, document.referrer, données postMessage) et l'assigne à un sink de navigation (window.location, document.location.href, location.replace(), location.assign()) sans validation d'hôte. Aucune réponse HTTP 30x côté serveur — la redirection est pilotée par exécution de script côté client.
Sinks de navigation déclenchant une redirection : window.location = url ; document.location = url ; window.location.href = url ; window.location.replace(url) ; window.location.assign(url) ; location = url ; window.open(url). Le meta refresh est quasi-équivalent : document.write('<meta http-equiv="refresh" content="0;url=' + url + '">''). Tous causent la navigation du navigateur vers l'URL fournie.
Sources contrôlables par l'attaquant : window.location.search (chaîne de requête), window.location.hash (fragment, jamais envoyé au serveur), document.referrer (header referrer), window.name (persiste entre pages), localStorage et sessionStorage (si l'attaquant peut écrire), données postMessage (messagerie cross-origin). La source hash est particulièrement intéressante car elle n'est pas loguée par les serveurs — les fragments URL sont navigateur-only.
webpack-dev-server incluait une overlay d'erreur de développement qui lisait des destinations de redirection depuis les paramètres URL sans validation. Un attaquant pouvait créer une URL pointant vers le serveur de développement déclenchant une redirection DOM-based vers une page de phishing. CVSS 6.1. Affectait uniquement les environnements de développement — ciblant des développeurs avec accès aux secrets de production.
Les scanners traditionnels injectent des payloads dans des paramètres HTTP et vérifient les réponses HTTP. Les redirections DOM-based ne produisent pas de réponse 30x côté serveur — la redirection n'est observable qu'en exécutant le JavaScript et en surveillant la navigation du navigateur. Détecter les redirections DOM-based requiert un rendu JavaScript (Chrome/Firefox headless avec Playwright ou Puppeteer) plus la surveillance des événements de navigation.
Certaines applications utilisent le fragment URL (location.hash) pour le routage SPA : if (location.hash) window.location = location.hash.slice(1). Un attaquant envoie une URL comme https://trusted.com/page#https://evil.com — le serveur ne voit jamais le fragment (supprimé avant la requête HTTP), mais le JavaScript côté client le lit et navigue vers evil.com. Cette technique est invisible dans les logs serveur.
Utilisez l'outil DOM Invader de Burp avec l'injection de canary activée. Naviguez l'application — DOM Invader trace les flux de données depuis les sources (location.search, location.hash, referrer) vers les sinks (location.href, location.replace). Quand un flux source-vers-sink atteint un sink de navigation, DOM Invader le signale. Pour les tests manuels : injectez une chaîne canary dans les paramètres URL et le hash, puis surveillez les événements de navigation du navigateur.
Les applications utilisant window.addEventListener('message', handler) et naviguant selon les données du message sont vulnérables si le gestionnaire ne valide pas message.origin. Une page de l'attaquant envoie : window.opener.postMessage({redirect: 'https://evil.com'}, '*'). Si le gestionnaire de message de la page cible assigne message.data.redirect à window.location sans validation, l'utilisateur est redirigé. Le wildcard * dans postMessage est le prérequis pour l'exploitation cross-origin.
Une redirection ouverte DOM-based a un CVSS de base typiquement de 4.3-6.1 (Moyen). CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N (4.3) pour du phishing pur. Quand chaîné avec vol de token via Referer ou exfiltration de cookie, le CVSS monte à 6.1-7.4. La nature côté client signifie que l'impact est limité à l'utilisateur qui suit le lien forgé.
Le hook useNavigate() de React Router v6 empêche la navigation externe par design — il ne gère que les routes same-origin. Cependant, le code utilisant window.location.href directement pour les redirections externes (pattern courant pour les flux post-auth) contourne complètement les garanties de sécurité de React Router. Recherchez les assignations directes window.location dans votre codebase React.