Vulnérabilités JWT (CWE-287) : confusion d'algorithme, force brute de secret HMAC et injection de clé — contournement complet de l'authentification.
TL;DR
hashcat -m 16500 contre rockyou.txtLes JSON Web Tokens (JWT) sont le mécanisme d'authentification sans état dominant dans les API modernes et les applications monopage. Chaque token est un triplet en-tête.charge_utile.signature encodé en base64url qui transporte des revendications d'identité — sub, role, email, exp — et est auto-vérifié par le serveur sans consulter un magasin de sessions central. Le modèle de sécurité repose entièrement sur la validation correcte de la signature par le serveur avant de faire confiance à toute revendication.
Les vulnérabilités JWT surviennent quand cette validation est absente, incomplète ou manipulable. Elles relèvent de CWE-287 (Authentification incorrecte) au niveau catégorie, avec des sous-classes couvrant CWE-347 (Vérification incorrecte d'une signature cryptographique), CWE-89 (Injection SQL via kid), CWE-78 (Injection de commande OS via kid) et CWE-345 (Vérification insuffisante de l'authenticité des données pour les attaques de source de clé). L'OWASP classe la catégorie sous A07:2021 (Identification et authentification incorrectes) comme classification principale.
Les attaques documentées ici ne sont pas théoriques. Six CVE avec des scores CVSS supérieurs à 8.0 ont été divulguées dans la fenêtre 2025-2026 — CVE-2026-22817 (Hono), CVE-2026-34950 (fast-jwt), CVE-2026-27804 (Parse Server), CVE-2026-23552 (Keycloak), CVE-2024-54150 (jose) et CVE-2024-48916 (Ceph) — dans les écosystèmes Node.js, Java, Python et Go simultanément.
Un token JWT comporte trois segments encodés en base64url séparés par des points. L'en-tête déclare l'algorithme et l'identifiant de clé. La charge utile transporte les revendications. La signature est un lien cryptographique.
La surface d'attaque se situe à chaque étape de ce flux :
header.alg plutôt qu'appliquer une liste d'autorisation, l'attaquant change alg en none ou HS256 pour contrôler l'algorithme de vérification.kid est utilisé dans une requête base de données ou chemin de fichier sans assainissement, l'attaquant injecte SQL ou traversée de chemin pour forcer le serveur à utiliser une clé contrôlée par l'attaquant.jku, x5u ou les champs jwk/x5c intégrés sont supportés sans liste d'autorisation, l'attaquant fournit une URL ou du matériel de clé pointant vers ses propres ressources cryptographiques.email ou des champs role personnalisés peuvent être falsifiés pour escalader les privilèges.L'attaque minimale canonique — le contournement alg:none — ne nécessite que l'encodage base64url :
import base64, json
def b64url(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
# Forger un token réclamant le rôle admin, aucune signature nécessaire
header = b64url(json.dumps({"alg": "none", "typ": "JWT"}).encode())
payload = b64url(json.dumps({"sub": "attaquant", "role": "admin", "exp": 9999999999}).encode())
forge = f"{header}.{payload}." # segment de signature vide| En-tête | Classe d'attaque | CVSS de base | Prévalence | CVE exemple |
|---|---|---|---|---|
alg | Confusion d'algorithme, contournement alg:none | 9,3–9,8 | Très courant | CVE-2015-9235, CVE-2026-22817 |
kid | Traversée de chemin, SQLi, NoSQLi, CMDi, LDAPi | 8,1–9,8 | Courant | CWE-89, CWE-78 |
jku | SSRF, détournement JWKS distant | 8,7–9,1 | Courant | CVE-2018-0114, CWE-918 |
x5u | SSRF, injection URL certificat | 8,7 | Rare | CWE-918, CWE-295 |
x5c | Certificat auto-signé intégré | 9,3 | Rare | CWE-347, CWE-295 |
jwk | Clé publique intégrée fiable sans condition | 9,3 | Courant | CVE-2018-0114 |
crit | Contournement extension critique RFC 7515 §4.1.11 | 7,5 | Émergent | CWE-347 |
cty | Chaîne XXE/désérialisation via type de contenu | 8,6 | Émergent | CWE-611, CWE-502 |
zip | Bombe de décompression JWE | 7,0 | Rare | CVE-2024-39689 |
Les paramètres d'en-tête jwk, jku et x5u devraient être entièrement désactivés dans la plupart des applications. Aucune application émettant des tokens depuis un magasin de clés contrôlé par le serveur ne doit accepter des clés spécifiées à distance ou intégrées. Si votre bibliothèque JWT supporte ces champs sans les avoir explicitement désactivés, vous êtes vulnérable aux attaques de classe CVE-2018-0114.
L'attaque alg:none contourne la vérification de signature en indiquant à la bibliothèque qu'aucun algorithme de signature n'a été utilisé. Les variantes de casse — None, NONE, nOnE, nonE, none (espace initial) — contournent les filtres de correspondance de chaînes simples. CVE-2026-34950 dans fast-jwt a réactivé cette classe via un contournement de regex utilisant un espace initial.
L'attaque de confusion RS256→HS256 est plus sophistiquée. RS256 vérifie avec une clé publique ; HS256 vérifie avec un secret partagé. En déclassant vers HS256 et en signant avec la clé publique RSA publique du serveur (disponible depuis JWKS ou TLS), l'attaquant fait vérifier HS256 contre sa propre clé publique connue — et accepte le token. Voir Attaque JWT alg:none.
HS256, HS384 et HS512 utilisent une clé symétrique — le même secret est utilisé pour signer et vérifier. Si le secret est un mot du dictionnaire, une courte chaîne ou une valeur inférieure à 32 octets, la force brute hors ligne avec hashcat -m 16500 le craque en quelques secondes. Voir Secret JWT faible.
Le paramètre kid identifie la clé à utiliser pour la vérification. Les implémentations non validées permettent l'injection SQL, la traversée de chemin, l'injection de commande OS, l'injection NoSQL et l'injection LDAP. Voir Injection JWT kid.
L'attaquant génère une paire de clés RSA, intègre sa clé publique dans l'en-tête JWT sous le champ jwk, signe avec sa clé privée, et le serveur vérifie contre la clé publique intégrée — acceptant le token. Voir Injection JWT jwk.
x5c intègre une chaîne de certificats DER ; x5u fournit une URL pour récupérer le certificat. x5u est simultanément un vecteur SSRF — la récupération HTTP côté serveur atteint des services internes. Voir Injection JWT x5c / x5u.
CVE-2026-22817 — Middleware JWK Hono (CVSS 8,2)
Le middleware JWT de Hono n'imposait pas d'exigence d'algorithme lors du traitement des tokens JWK. Un token sans champ alg dans le JWK causait une confusion d'algorithme. Le pattern correspond à la classe JWKSAlgAbsent.
CVE-2026-34950 — Réactivation confusion algorithme fast-jwt (CVSS 9,1)
fast-jwt avait patché CVE-2023-48223 via une regex. Le contournement 2026 utilisait un espace initial ( HS256) — la regex échouait et la bibliothèque acceptait l'algorithme de l'en-tête directement.
CVE-2026-27804 — Confusion d'algorithme OAuth2 Parse Server (CVSS 9,8)
L'intégration OAuth2 de Parse Server avec Google et Apple n'imposait pas le champ alg dans le JWKS, permettant le contournement d'authentification.
CVE-2026-23552 — Relais de token cross-realm Keycloak (CVSS 9,1)
Keycloak acceptait un token émis pour le realm A par le realm B quand la revendication iss n'était pas strictement validée par realm.
CVE-2024-54150 — jose (Cisco) Confusion RS256→HS256 (CVSS 9,1) La bibliothèque jose utilisée dans les produits Cisco acceptait des tokens signés HS256 alors que configurée pour RS256.
CVE-2022-21449 — Psychic Signatures Java (CVSS 7,5) Java SE 15-18 acceptait une signature ECDSA avec r=0 et s=0 comme valide pour n'importe quel message. Découvert par Neil Madden (ForgeRock).
CVE-2018-0114 — Injection jwk node-jose (CVSS 9,3)
node-jose faisait confiance sans condition à la clé publique intégrée dans l'en-tête JWT jwk. La classe d'attaque a été redécouverte dans des dizaines de bibliothèques depuis.
Dérive de revendication mutable Allianz/Salesforce (2025)
ShinyHunters a exploité des applications identifiant les utilisateurs par la revendication email JWT plutôt que par le sub immuable. Un attaquant changeait son email inscrit chez l'IdP pour correspondre à celui d'une victime — prise de contrôle de compte complète sans toucher au matériel cryptographique.
La dérive de revendication mutable est sous-détectée en revue de code. Toute requête base de données utilisant jwt_claims["email"] au lieu de jwt_claims["sub"] est un vecteur potentiel de prise de contrôle de compte. La revendication sub (RFC 7519 §4.1.2) est garantie immuable au sein d'un émetteur ; email, username et preferred_username sont mutables chez le fournisseur d'identité.
alg, kid, jku, x5u, jwk et x5c.{"alg":"none","typ":"JWT"}, supprimer la signature (garder le point final), renvoyer. Tester les variantes de casse : None, NONE, nOnE, nonE, none./.well-known/jwks.json ou via openssl s_client. Utiliser jwt_tool : python3 jwt_tool.py "$TOKEN" -X k -pk pubkey.pem -I -pc role -pv admin.kid à ../../dev/null puis à des charges SQL (x' UNION SELECT 'AAAAAAAAA' -- -). Signer avec un secret HMAC vide.hashcat -m 16500 token.txt /usr/share/wordlists/rockyou.txt.jwt_tool (v2.2.6+) en mode playbook couvrant 20+ attaques :
python3 /opt/jwt_tool/jwt_tool.py "$TOKEN" -M pb -npBurp Suite JWT Editor détecte automatiquement les points d'injection jku, x5c, x5u, jwk et kid.
hashcat pour la récupération du secret HMAC :
hashcat -m 16500 -a 0 token_capture.txt /usr/share/wordlists/rockyou.txtBreachVex détecte les vulnérabilités JWT via une séquence d'attaque multi-étapes : il découvre les endpoints JWKS sur les chemins standards, établit un oracle d'authentification vérifié (triple-baseline : valide→200, sans token→401, corrompu→401), teste toutes les variantes de confusion d'algorithme dont les 8 permutations de casse alg:none, sonde l'injection de source de clé avec des rappels hors-bande pour le SSRF jku/x5u, fuzze kid avec des charges de traversée de chemin et SQLi/CMDi, et exécute le craquage HMAC sur les tokens symétriques. Chaque vulnérabilité exige une validation différentielle du corps de réponse avant signalement.
La défense la plus importante — refuser de faire confiance au champ alg de l'en-tête JWT :
# MAUVAIS — fait confiance à l'algorithme de l'en-tête
import jwt
decoded = jwt.decode(token, key, algorithms=jwt.get_unverified_header(token)["alg"])
# BON — liste d'autorisation explicite côté serveur
ALGORITHMES_AUTORISES = ["RS256"]
decoded = jwt.decode(token, cle_publique, algorithms=ALGORITHMES_AUTORISES)// MAUVAIS — jsonwebtoken fait confiance à l'algorithme de l'en-tête
const payload = jwt.verify(token, secret)
// BON — liste d'autorisation explicite
const payload = jwt.verify(token, cle_publique, { algorithms: ['RS256'] })# MAUVAIS — revendication mutable, vulnérable à la prise de contrôle de compte
utilisateur = db.users.find_one({"email": decoded["email"]})
# BON — sub immuable selon RFC 7519 §4.1.2
utilisateur = db.users.find_one({"oidc_sub": decoded["sub"]})import re
# MAUVAIS — traversée de chemin et vecteur SQLi
cle = open(f"/keys/{header['kid']}").read()
# BON — regex stricte + requête paramétrée
KID_PATTERN = re.compile(r'^[a-zA-Z0-9_-]{1,64}$')
if not KID_PATTERN.fullmatch(header.get("kid", "")):
raise ValueError("Format kid invalide")
ligne = db.execute("SELECT k FROM keys WHERE id = $1", (header["kid"],)).fetchone()ENTETES_INTERDITS = {"jku", "x5u", "jwk", "x5c"}
def verifier_jwt(token: str, alg_attendu: str, cle_publique) -> dict:
entete = jwt.get_unverified_header(token)
trouves = ENTETES_INTERDITS & entete.keys()
if trouves:
raise SecurityError(f"Paramètres d'en-tête JWT interdits : {trouves}")
if entete.get("alg") != alg_attendu:
raise SecurityError("Algorithme inattendu")
return jwt.decode(token, cle_publique, algorithms=[alg_attendu])Pour les systèmes distribués (microservices, instances backend multiples), utilisez des clés asymétriques (RS256 ou ES256). Seul le service de signature détient la clé privée ; tous les services de vérification ne détiennent que la clé publique. Un service compromis ne peut pas forger de nouveaux tokens. HMAC symétrique (HS256) exige que chaque service partage le secret — chaque service devient un oracle de signature potentiel en cas de compromission.
La confusion d'algorithme survient quand le serveur fait confiance au champ alg de l'en-tête JWT plutôt que d'appliquer une liste d'autorisation. Un attaquant change alg de RS256 à HS256 et signe le token avec la clé publique du serveur comme secret HMAC — contournant entièrement la vérification de signature. Les bibliothèques comme PyJWT et jsonwebtoken corrigent cela en exigeant un paramètre algorithms= explicite.
L'attaque alg:none (CVE-2015-9235) définit l'en-tête d'algorithme à 'none', produisant un token non signé avec une signature vide. Les bibliothèques qui faisaient confiance à l'algorithme déclaré par l'en-tête acceptaient ces tokens comme valides. Les variantes de casse — None, NONE, nOnE, nonE — contournent les filtres de correspondance simple. La correction consiste à rejeter tout token dont alg n'est pas dans une liste d'autorisation côté serveur.
Le paramètre kid identifie la clé à utiliser pour la vérification. Si l'application utilise kid dans une requête base de données ou un chemin de fichier sans assainissement, un attaquant peut injecter du SQL, une traversée de chemin vers /dev/null, une injection de commande OS, une injection NoSQL ou LDAP pour contrôler la clé de vérification et forger des tokens.
L'injection JWK (CVE-2018-0114) intègre une clé publique contrôlée par l'attaquant directement dans l'en-tête JWT sous le champ jwk. Les bibliothèques vulnérables vérifient la signature du token contre cette clé intégrée plutôt que contre un magasin de clés côté serveur, permettant à l'attaquant de signer n'importe quel token avec sa propre clé privée.
x5u est un paramètre d'en-tête JWT spécifiant une URL depuis laquelle récupérer le certificat de signature. Si le serveur récupère cette URL sans validation, un attaquant pointe x5u vers son propre certificat, signe le token avec la clé privée correspondante, et le serveur l'accepte. C'est également un vecteur SSRF — la récupération HTTP côté serveur peut atteindre des services internes.
Commande : hashcat -m 16500 token.txt /usr/share/wordlists/rockyou.txt. Le mode 16500 cible les tokens JWT HMAC-SHA256/384/512. Hashcat force la clé HMAC en mode hors ligne sans contacter le serveur. Les secrets de moins de 32 octets se crackent en quelques secondes à minutes contre rockyou.txt.
jwt_tool (v2.2.6+ par ticarpi) couvre 20+ vecteurs d'attaque en mode playbook (-M pb). L'extension Burp JWT Editor auto-détecte les points d'injection jku/x5c/x5u/jwk/kid. hashcat -m 16500 gère la force brute HMAC. sig2n dérive les clés publiques RSA de deux signatures de tokens valides. nuclei dispose de templates JWT dans http/misconfiguration/jwt-*.
CVE-2026-34950 (fast-jwt, CVSS 9.1), CVE-2026-22817 (Hono, CVSS 8.2), CVE-2026-27804 (Parse Server, CVSS 9.8), CVE-2026-23552 (Keycloak cross-realm, CVSS 9.1), CVE-2024-54150 (jose Cisco, CVSS 9.1), CVE-2024-48916 (Ceph RadosGW, CVSS 9.8), CVE-2022-21449 (Psychic Signatures Java, CVSS 7.5), CVE-2018-0114 (node-jose jwk, CVSS 9.3).
L'attaque de confusion d'algorithme RS256→HS256 exploite les bibliothèques JWT qui font confiance au header alg pour choisir la fonction de vérification. Le token RS256 est signé avec une clé privée RSA et vérifié avec la clé publique. Un attaquant change alg à HS256, signe le payload avec la clé publique RSA (souvent exposée via /.well-known/jwks.json) en tant que secret HMAC, et l'implémentation vulnérable utilise alors la clé publique comme secret HMAC, validant la signature forgée. Toutes les bibliothèques qui ne forcent pas un allowlist d'algorithmes au moment de la vérification sont vulnérables. Voir jwt-alg-none pour la chaîne complète.
La dérive de revendication mutable survient quand une application identifie les utilisateurs par la revendication email (modifiable chez le fournisseur d'identité) au lieu de la revendication sub immuable (RFC 7519 §4.1.2). Un attaquant change son email chez l'IdP pour correspondre à celui d'une victime, reçoit un nouveau token ID avec cet email, et l'application mappe l'attaquant sur le compte de la victime.
Keycloak CVE-2026-23552 permettait à un token émis pour le realm A d'être accepté par le realm B car la revendication iss n'était pas validée strictement par realm. Un attaquant avec un compte sur le realm A pouvait forger l'accès aux services du realm B.
Les secrets JWT HMAC doivent comporter au moins 32 octets (256 bits) de données aléatoires cryptographiques. Utilisez : Python : secrets.token_bytes(32). Node.js : crypto.randomBytes(32).toString('hex'). Ne jamais utiliser de chaînes lisibles par l'homme, mots de passe ou valeurs prévisibles. Rotation tous les 90 jours.
Les cookies avec HttpOnly, Secure, SameSite=Strict et le préfixe __Host- sont plus sécurisés que localStorage. localStorage est accessible en JavaScript — toute faille XSS peut exfiltrer les tokens. Les cookies HttpOnly sont immunisés contre l'accès JavaScript.
JWE supporte la compression DEFLATE via le paramètre d'en-tête zip. CVE-2024-39689 dans la bibliothèque jose Python permettait à un attaquant non authentifié de créer un JWE avec un payload décompressé massivement gonflé, épuisant la mémoire serveur.
CVE-2022-21449 affectait Java SE 15-18 : le vérificateur de signature ECDSA acceptait une signature avec r=0 et s=0 comme valide pour n'importe quel message. Un JWT forgé avec une signature ECDSA vide passait la vérification sur Java 15-18. Corrigé dans la mise à jour CPU d'avril 2022.
A02 (Défaillances cryptographiques) pour la confusion d'algorithme, secrets faibles et attaques de source de clé. A07 (Identification et authentification incorrectes) pour la falsification de token contournant l'authentification. A03 (Injection) pour kid SQLi, kid CMDi et kid LDAPi.