Open redirect (CWE-601) : un attaquant détourne un paramètre de redirection pour envoyer les utilisateurs vers des URLs contrôlées (phishing, OAuth).
?next=, ?redirect=, ?goto= ; OAuth redirect_uri ; JavaScript window.location\\, %09, @endsWith ni correspondance par préfixeUne redirection ouverte est une classe de vulnérabilité sous CWE-601 (URL Redirection to Untrusted Site) où une application accepte une URL dans un paramètre de requête et redirige le navigateur vers cette destination sans valider que la cible est un hôte autorisé. Le mécanisme de redirection existe pour des raisons légitimes — renvoyer l'utilisateur vers la page qu'il essayait d'atteindre après connexion, ou le retourner vers l'application appelante après un flux OAuth. La vulnérabilité apparaît quand l'URL de destination est prise telle quelle depuis une entrée contrôlée par l'attaquant.
La forme serveur émet un HTTP 302 (ou 301, 303, 307) avec un header Location contenant l'URL de l'attaquant. Le navigateur le suit automatiquement. La forme côté client utilise JavaScript : window.location = valeurParam. Les deux relèvent d'OWASP A01:2025 (Broken Access Control) car elles permettent de rediriger les utilisateurs hors de la limite de contrôle de l'application.
Une redirection ouverte obtient CVSS 6.1 en isolation — Moyen, souvent noté Faible dans les programmes avec des seuils élevés. Le vrai multiplicateur de gravité vient du chaînage. Une redirection ouverte sur un endpoint d'autorisation OAuth laisse fuiter le code d'autorisation : CVE-2019-3778 de Spring Security OAuth est le cas d'école, le NVD notant la fuite du code 6.5 alors que la prise de contrôle de compte qu'elle permet est ce que les programmes paient (NVD, CVE-2019-3778). Une redirection ouverte sur un domaine dans une allowlist SSRF est ce qu'exploite CVE-2025-4123 de Grafana — la redirection chaînée avec une traversée de chemin côté client jusqu'à un XSS de plugin frontend et, avec le plugin Image Renderer installé, un SSRF en lecture complète, noté 7.6 (Élevé) par Grafana (avis de sécurité Grafana, 21 mai 2025). CWE-601 est l'une des classes les plus régulièrement rapportées en bug bounty : l'index CWE discovery de HackerOne recense 17 199 signalements uniques de redirection ouverte depuis l'origine — 7e rang des 25 classes de faiblesse que l'index publie — avec 280 nouveaux signalements sur les douze semaines précédant le 15 octobre 2026, soit un recul de 1 % sur la période (index CWE discovery de HackerOne, relevé du 15 octobre 2026). Dans la base CVE, cette classe est bien plus rare : l'étude NDSS 2025 "Do (Not) Follow the White Rabbit: Challenging the Myth of Harmless Open Redirection" (Khodayari, Glauber et Pellegrino) la mesure à 1 % des 237 470 entrées CVE, contre 37 % pour le XSS, et a escaladé plus de 11,5 % des redirections ouvertes trouvées, sur 38 % des sites touchés, en XSS, CSRF ou fuite d'information (NDSS Symposium 2025, article 2025-523).
Le motif de base est une redirection côté serveur via paramètre :
GET /login?next=https://attacker.com HTTP/1.1
Host: banque-de-confiance.com
HTTP/1.1 302 Found
Location: https://attacker.comLe navigateur suit le header Location vers attacker.com. L'utilisateur voit banque-de-confiance.com dans sa barre d'adresse pendant la requête initiale — la confiance est établie avant que la redirection se produise.
sequenceDiagram
participant U as Utilisateur
participant B as Navigateur
participant S as banque-de-confiance.com
participant A as attacker.com
U->>B: Clique sur lien de phishing vers banque-de-confiance.com/login?next=https://attacker.com
B->>S: GET /login?next=https://attacker.com
Note over B: L'utilisateur voit banque-de-confiance.com dans la barre d'adresse
S-->>B: 302 Location: https://attacker.com
B->>A: GET / (le navigateur suit la redirection)
A-->>B: Page de phishing
Note over U: L'utilisateur croit être sur banque-de-confiance.comLa chaîne de redirection se déroule en trois étapes :
req.query.next et le passe directement à res.redirect() sans validation d'hôte.| Variante | Technique | CVSS (isolé) | Chaîne élevée |
|---|---|---|---|
| Paramètre URL côté serveur | ?next=https://evil.com dans Location 30x | 6.1 | 8.6 avec SSRF |
| Header (Host/X-Forwarded-Host) | Override Host → construction URL redirection | 6.1 | 8.1 avec reset poisoning |
| JavaScript DOM | window.location = paramètre | 4.3 | 7.4 avec vol cookie |
OAuth redirect_uri | Bypass exact match → vol de code | 8.6 | 9.8 (ATO complet) |
| Chaîne SSRF | Domaine de confiance redirige vers IMDS | 8.6 | 9.1 avec identifiants cloud |
| Chemin (path-based) | /go/https://evil.com | 6.1 | 7.5 |
from urllib.parse import urlparse
# Python voit ceci comme un chemin sans netloc — "sûr"
urlparse("\\evil.com").netloc # Retourne '' — Python pense que netloc est vide
urlparse("%09https://evil.com").netloc # Retourne '' — tab supprimé, pas de schéma
# Mais WHATWG (Chrome/Firefox) normalise :
# \\evil.com → //evil.com → https://evil.com (navigateur suit)
# %09https://evil.com → https://evil.com (navigateur décode le tab, analyse l'URL absolue)Payloads de bypass actifs (liste Diverto 2024) :
//attacker.com # Protocol-relative — les deux slashes requis
https:////attacker.com # Slashes multiples — WHATWG élimine les extras
https:\attacker.com # Antislash — WHATWG normalise en /
https://trusted.com@evil.com # Userinfo — trusted.com est le nom d'utilisateur
https://trusted.com.evil.com # Abus de suffixe — valide l'hostname complet, pas endsWith
%EF%BC%8E # Point pleine largeur Unicode — normalisation → .url.startsWith("https://trusted.com") est contournable avec https://trusted.com.evil.com et https://trusted.com@evil.com. Ne validez jamais les destinations de redirection par préfixe ou suffixe de chaîne. Parsez l'URL, extrayez le hostname, et comparez contre une allowlist d'exacte correspondance.
CVE-2025-4123 — Grafana (CVSS 6.1 (NVD))
L'avis de Grafana (21 mai 2025) décrit un XSS construit à partir de deux failles : une traversée de chemin côté client combinée à une redirection ouverte. La redirection envoie la victime vers un site qui héberge un plugin frontend, lequel exécute du JavaScript arbitraire — aucune permission d'éditeur requise, et avec l'accès anonyme activé le XSS se déclenche sans session. Grafana le note 7.6 (Élevé, CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L) et a corrigé dans les versions 10.4.18, 11.2.9, 11.3.6, 11.4.4, 11.5.4, 11.6.1 et 12.0.0 (builds +security-01). Avec le plugin Grafana Image Renderer installé, la même redirection atteint un SSRF en lecture complète ; la Content-Security-Policy par défaut (connect-src) bloque le XSS dans un déploiement standard. L'analyse du NVD sur le même enregistrement donne 6.1 (Moyen) — la vue de la redirection isolée. CVE-2025-6023 a répété le schéma : une redirection ouverte chaînée avec une traversée de chemin vers un XSS, introduite dans Grafana 11.5.0 et corrigée dans 11.3.8, 11.4.6, 11.5.6, 11.6.3 et 12.0.2 (builds +security-01) (CNA Grafana, CVE-2025-6023, publié le 18 juillet 2025).
CVE-2025-69725 — go-chi RedirectSlashes (CVSS 4.7 Moyen)
Le middleware RedirectSlashes de go-chi normalisait les chemins à double slash : GET //evil.com était traité comme un chemin nécessitant normalisation, produisant 302 Location: //evil.com. Les navigateurs résolvent les URLs protocol-relative (//evil.com) en https://evil.com. La faille a été introduite dans chi 5.2.2 ; toute application Go avec RedirectSlashes activé et sans validation du Host était affectée. CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N (CNA MITRE, CVE-2025-69725, publié le 19 février 2026).
CVE-2024-23832 — Mastodon (CVSS 9.8 (NVD))
Ce n'est pas une redirection ouverte, et la faille figure dans cette liste précisément pour cette raison : le défaut de Mastodon est une validation d'origine insuffisante, qui permettait à un attaquant d'usurper et de prendre le contrôle de n'importe quel compte distant (NVD, CVE-2024-23832, publié le 1er février 2024). L'avis de GitHub évalue l'impact de confidentialité Faible ; la métrique primaire du NVD donne 9.8 avec C:H. Deux sources, deux lectures du même enregistrement — c'est pourquoi chaque score cité dans cet article nomme sa source.
CVE-2019-3778 — Spring Security OAuth (CVSS 6.5 Moyen)
Le cas d'école de la fuite de code par redirection ouverte : les versions 2.3 avant 2.3.5, 2.2 avant 2.2.4, 2.1 avant 2.1.4 et 2.0 avant 2.0.17 acceptaient un redirect_uri manipulé, ce qui faisait rediriger le user-agent du propriétaire de ressource vers une URI contrôlée par l'attaquant avec le code d'autorisation fuité (NVD, CVE-2019-3778, publié le 7 mars 2019). Le NVD note la fuite 6.5 — ce n'est pas la redirection qui la rend dangereuse, c'est le code qu'elle transmet.
gf redirect (tomnomnom/gf) contre les données Wayback Machine.Location pour du contenu fourni par l'utilisateur.https://example.com d'abord, puis votre URL Burp Collaborator ou Interactsh.redirect_uri et observer si le code d'autorisation est ajouté à l'URL forgée avant la redirection.//attacker.com, \attacker.com, %09https://attacker.com, https://attacker.com@trusted.com, https://trusted.com.attacker.com.Location: /dashboard et Location: https://target.com/path ne sont PAS des redirections ouvertes.# OpenRedireX — fuzzer async avec 50+ payloads de bypass
echo "https://target.com/login?next=FUZZ" | openredirex -p /path/to/payloads.txt
# nuclei — template CVE
nuclei -u https://target.com -t http/cves/2025/CVE-2025-4123.yaml
# Dalfox v3+ — mode open-redirect
dalfox url "https://target.com/login?next=1" --open-redirectBreachVex détecte les redirections ouvertes via des sondes canary out-of-band injectées dans les paramètres de redirection découverts. Un finding confirmé nécessite un header Location 30x contenant le domaine canary, plus une vérification eTLD+1 pour éliminer les faux positifs same-origin.
# Flask — BONNE PRATIQUE
from urllib.parse import urlparse
ALLOWED_HOSTS = frozenset({"app.example.com", "dashboard.example.com"})
def is_safe_redirect(url: str, fallback: str = "/dashboard") -> str:
if not url:
return fallback
# Rejeter les caractères de contrôle
if any(c in url for c in ["\r", "\n", "\t", "\x00", "\\"]):
return fallback
parsed = urlparse(url)
# Autoriser les URLs relatives commençant par / (pas //)
if not parsed.netloc and url.startswith("/") and not url.startswith("//"):
return url
# Les URLs externes doivent être sur l'allowlist
if parsed.netloc not in ALLOWED_HOSTS:
return fallback
return url// Express.js — BONNE PRATIQUE
const ALLOWED_HOSTS = new Set(["app.example.com", "dashboard.example.com"]);
function isSafeRedirect(url) {
if (!url) return false;
if (url.startsWith("/") && !url.startsWith("//")) return true;
try {
// Constructeur WHATWG — même parseur que les navigateurs
const parsed = new URL(url);
return ALLOWED_HOSTS.has(parsed.hostname);
} catch {
return false;
}
}
app.get("/redirect", (req, res) => {
const next = req.query.next ?? "/home";
res.redirect(isSafeRedirect(next) ? next : "/home");
});# Stocker la destination avant la redirection — aucune URL dans le paramètre
import secrets
import redis
redis_client = redis.Redis()
@app.route("/pre-login")
def pre_login():
state = secrets.token_urlsafe(32)
redis_client.set(f"redirect:{state}", "/user/dashboard", ex=300)
return redirect(f"/login?state={state}")
@app.route("/post-login")
def post_login():
state = request.args.get("state", "")
raw = redis_client.get(f"redirect:{state}")
destination = raw.decode() if raw else "/dashboard"
redis_client.delete(f"redirect:{state}")
return redirect(destination)# MAUVAISE PRATIQUE — correspondance par préfixe (contournable)
def validate_redirect_uri(requested: str, registered: str) -> bool:
return requested.startswith(registered)
# BONNE PRATIQUE — comparaison exacte de chaîne RFC 6749 §3.1.2
def validate_redirect_uri(requested: str, registered: str) -> bool:
return requested == registered # Correspondance exacte uniquementL'enregistrement wildcard redirect_uri (*.example.com) est une misconfiguration critique. Un attaquant peut enregistrer evil.example.com (si l'enregistrement de sous-domaine est possible) ou exploiter toute redirection ouverte sur n'importe quel sous-domaine. La RFC 6749 §3.1.2 interdit explicitement la correspondance par wildcard.
Une redirection ouverte (CWE-601) se produit quand une application accepte une URL contrôlée par l'utilisateur et redirige le navigateur vers cette destination sans vérifier qu'elle est autorisée. La forme la plus simple : /login?next=https://evil.com — le serveur émet un 302 Location: https://evil.com et le navigateur suit. La confiance vient du domaine d'origine ; le danger réside dans la destination.
Le CVSS de base est 6.1 (Moyen). En isolation, une redirection ouverte est notée Faible à Moyen. La gravité se multiplie par chaînage : sur un endpoint d'autorisation OAuth, elle laisse fuiter le code d'autorisation — CVE-2019-3778 de Spring Security OAuth envoyait le user-agent et le code vers une URI contrôlée par l'attaquant, noté 6.5 par le NVD avant même de compter ce que le code permet (NVD, CVE-2019-3778). Dans la variante chaînée avec SSRF, la redirection contourne l'allowlist : CVE-2025-4123 de Grafana atteint un SSRF en lecture complète quand le plugin Image Renderer est installé (avis de sécurité Grafana, 21 mai 2025).
La redirection serveur émet une réponse HTTP 30x avec un header Location pointant vers l'URL de l'attaquant — le navigateur suit automatiquement. La redirection client (DOM-based) utilise JavaScript : window.location = valeurContrôlée. La redirection se produit dans le navigateur sans aller-retour serveur. Les deux relèvent de CWE-601 ; la variante DOM est plus difficile à détecter automatiquement car la logique est en JavaScript.
Dans OAuth 2.0, le serveur d'autorisation ajoute le code d'autorisation au redirect_uri avant d'y rediriger le navigateur. Si redirect_uri contient une redirection ouverte sur un domaine de confiance (ex: https://trusted.com/redirect?next=https://evil.com), le code est ajouté à cette URL. Le navigateur suit vers evil.com avec le code en paramètre — l'attaquant l'échange contre des tokens d'accès. CVE-2019-3778 de Spring Security OAuth est exactement ce cas : une requête d'autorisation forgée avec un redirect_uri manipulé faisait rediriger le user-agent du propriétaire de ressource vers une URI contrôlée par l'attaquant, avec le code d'autorisation dans l'URL (NVD, CVE-2019-3778, publié le 7 mars 2019).
Des parseurs différents interprètent la même chaîne différemment. WHATWG (Chrome, Firefox) normalise les antislashs en slashs — //evil.com et \evil.com se résolvent tous deux en https://evil.com. Python urllib.parse traite \evil.com comme un chemin relatif. Node.js legacy url.parse traite //evil.com comme protocol-relative. Un backend qui valide avec Python peut accepter \evil.com comme 'sûr' tandis que le navigateur le résout vers un domaine externe.
Les défenses SSRF autorisent souvent des domaines de confiance spécifiques. Si le serveur récupère une URL d'un domaine de confiance, et que ce domaine a une redirection ouverte, le serveur suit la redirection vers un endpoint interne ou cloud. CVE-2025-4123 de Grafana est cette chaîne en production : la redirection ouverte, combinée à une traversée de chemin côté client, envoie la victime vers un site hébergeant un plugin frontend qui exécute du JavaScript arbitraire, et avec le plugin Image Renderer installé la même redirection atteint un SSRF en lecture complète (avis de sécurité Grafana, 21 mai 2025 ; NVD, CVE-2025-4123).
En priorité : next, redirect, redirect_uri, redirect_url, url, goto, target, dest, destination, return, returnTo, forward, link, continue, callback, success, failure, r, u. Endpoints suspects : /logout, /login, /oauth/authorize, /sso, /redirect, /go, /out, /external, /r/<id>. Vérifiez aussi le header Location dans toutes les réponses 30x pour du contenu utilisateur reflété.
Parsez le header Location et extrayez l'eTLD+1. Si l'eTLD+1 est identique à celui de la cible, ce n'est pas une redirection ouverte — les redirections vers le même domaine sont légitimes. Excluez aussi les domaines CDN (*.cloudfront.net, *.akamaiedge.net) appartenant à la cible et les partenaires OAuth déclarés. Une redirection ouverte confirmée a Location: https://<eTLD+1-externe-non-autorisé> avec un statut 30x.
La RFC 6749 §3.1.2 exige que la comparaison redirect_uri soit une correspondance exacte de chaîne. Pas de correspondance par préfixe, pas de wildcard, pas de tolérance pour les traversées de chemin. L'URI complet incluant schéma, hôte, chemin et chaîne de requête doit correspondre caractère par caractère. CVE-2019-3778 de Spring Security OAuth vient exactement de cet écart : son DefaultRedirectResolver laissait passer un redirect_uri manipulé et le serveur d'autorisation redirigeait le user-agent vers l'attaquant avec le code attaché (NVD, CVE-2019-3778).
Le middleware RedirectSlashes de go-chi normalisait les chemins à double slash. Une requête GET //evil.com était interprétée comme un chemin nécessitant normalisation, produisant 302 Location: //evil.com. Les navigateurs résolvent les URLs protocol-relative — //evil.com devient https://evil.com. La faille a été introduite dans chi 5.2.2 ; toute application Go avec RedirectSlashes activé et sans validation du Host était affectée. MITRE, en tant que CNA, le note 4.7 (Moyen) avec CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N.
L'avis de Grafana pour CVE-2025-4123 (21 mai 2025) décrit un XSS construit à partir de deux failles : une traversée de chemin côté client combinée à une redirection ouverte. La redirection envoie la victime vers un site qui héberge un plugin frontend, lequel exécute du JavaScript arbitraire — aucune permission d'éditeur requise, et avec l'accès anonyme activé le XSS se déclenche sans session. Grafana le note 7.6 (Élevé, CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L) et a corrigé dans les versions de sécurité 10.4.18, 11.2.9, 11.3.6, 11.4.4, 11.5.4, 11.6.1 et 12.0.0 ; avec le plugin Grafana Image Renderer installé, la même redirection atteint un SSRF en lecture complète. CVE-2025-6023 a répété le schéma — une redirection ouverte chaînée avec une traversée de chemin vers un XSS, introduite dans Grafana 11.5.0 et corrigée dans 11.3.8, 11.4.6, 11.5.6, 11.6.3 et 12.0.2 (CNA Grafana, CVE-2025-6023, publié le 18 juillet 2025).
Python : utilisez urllib.parse.urlparse pour extraire le hostname, puis vérifiez hostname == 'expected.com' (pas endswith). Rejetez toute URL où le schéma n'est pas http/https et le netloc n'est pas sur l'allowlist. Node.js/Express : utilisez le constructeur URL (new URL(input)), vérifiez url.hostname contre un Set explicite de noms d'hôte autorisés. N'utilisez jamais string.startsWith ni string.includes pour la validation — ces méthodes sont contournées par l'abus userinfo (https://allowed.com@evil.com).
Une redirection ouverte CWE-601 isolée est CVSS 6.1 (Moyen, CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N). Chaînée avec le vol de code d'autorisation OAuth, la gravité suit ce que le code permet : le NVD note CVE-2019-3778 de Spring Security OAuth 6.5 pour la fuite du code, tandis que CVE-2024-23832 de Mastodon — une validation d'origine insuffisante, pas une redirection — est 9.8 (NVD), parce qu'elle finit en prise de contrôle de n'importe quel compte distant. Le multiplicateur de chaîne est la clé — évaluez toujours la chaîne la plus sévère, pas la redirection isolée.
BreachVex utilise une stratégie de sondes out-of-band (OOB) : des URLs canary uniques sont injectées dans les paramètres de redirection découverts. Une redirection ouverte confirmée nécessite une réponse 30x avec Location contenant le domaine canary, ou un callback DNS/HTTP confirmant la récupération côté serveur. Le scan applique la normalisation eTLD+1 pour éliminer les faux positifs same-origin et vérifie la liste des partenaires CDN/OAuth avant d'émettre un finding.