Les JSON Web Tokens sont fréquemment mal configurés, permettant des attaques allant de la confusion d'algorithme au brute-force de secret.
TL;DR
alg:none, downgrades RS256→HS256 et injection kid contournent entièrement la vérification de signatureLes JSON Web Tokens (JWT) sont des bearer tokens stateless : une chaîne en-tête.charge.signature signée. Le client les envoie dans les en-têtes Authorization: Bearer <token>, et le serveur valide la signature pour garantir l'intégrité. Cependant, une gestion JWT mal configurée figure parmi les défauts d'authentification les plus courants — apparaissant dans OWASP A07:2021 (Identification and Authentication Failures) pour les erreurs de conception et A02:2021 (Cryptographic Failures) pour les classes de contournement de signature.
Le risque principal : si la validation de la signature est contournée ou affaiblie, les attaquants peuvent forger des tokens en tant que n'importe quel utilisateur — accordant un accès non autorisé, une élévation de privilèges ou un déplacement latéral entre services. Comme les JWT sont généralement auto-contenus (aucun état de session côté serveur à invalider), un seul token forgé peut rester valide pendant toute la durée de vie de l'original — parfois des heures ou des jours.
Les vulnérabilités JWT découlent de trois propriétés structurelles du format qui, individuellement, sont voulues par conception mais produisent collectivement des mauvaises configurations récurrentes :
alg vit dans l'en-tête contrôlé par l'attaquant. Toute bibliothèque doit lire la déclaration d'algorithme avant de valider une quelconque signature, parce que l'algorithme indique à la bibliothèque comment valider. Si la bibliothèque fait confiance à ce champ sans allowlist explicite, l'attaquant contrôle le chemin de vérification."secret" sont textuellement indiscernables dans un fichier Docker .env. Il n'existe aucune vérification à la compilation, au lint ou à l'exécution qui signalerait JWT_SECRET=changeme — mais hashcat -m 16500 le récupère en millisecondes.aud, l'autre non.Le résultat est que l'exploitation JWT consiste rarement à briser la cryptographie — il s'agit de trouver une bibliothèque, un service ou un chemin de code où les contrôles ont été mal configurés ou entièrement ignorés.
| Classe | Variante | CVE principal | CVSS | Difficulté |
|---|---|---|---|---|
| Suppression de signature | alg:none | CVE-2015-9235, CVE-2026-34950 | 9.8 | Triviale |
| Confusion de clé | RS256 → HS256 | CVE-2016-10555, CVE-2022-29217 | 9.1 | Facile |
| Brute-force | Secret HMAC faible | n/a (problème de config) | 8.8 | Facile avec GPU |
| Injection d'en-tête | Paramètre kid | CVE-2018-0114 | 8.1 | Moyenne |
| Injection d'en-tête | jwk / x5c / x5u | Variantes CVE-2018-0114 | 7.5 | Moyenne |
Chaque variante dispose d'une sous-page dédiée dans cette section. La cause racine commune aux cinq est faire confiance à des métadonnées de token contrôlées par l'attaquant lors de la vérification.
Trois probes confirment ou écartent les mauvaises configurations les plus courantes :
# 1. alg:none — supprimer la signature
echo '{"alg":"none","typ":"JWT"}' | base64url
echo '{"sub":"admin","role":"admin"}' | base64url
# Concaténer : <en-tête>.<charge>. (point final, signature vide)
curl -H "Authorization: Bearer <forge>" https://target/api/me
# 2. Secret HMAC faible
hashcat -m 16500 token_capture.txt /usr/share/wordlists/rockyou.txt
# 3. Confusion RS256 → HS256
# Signer la charge avec la clé publique RSA du serveur comme secret HMAC
python3 -c "import jwt; print(jwt.encode({'role':'admin'}, open('server.pub').read(), algorithm='HS256'))"Une réponse 200 sur la probe 1 ou 3, ou un secret récupéré sur la probe 2, indique une mauvaise configuration exploitable. Les endpoints de production doivent retourner 401 sur les trois.
# SÛR — allowlist d'algorithmes explicite, liaison d'audience, application d'expiration
import jwt
from jwt import InvalidTokenError
try:
decoded = jwt.decode(
token,
public_key,
algorithms=["RS256"], # allowlist explicite — ne jamais faire confiance à header.alg
audience="api.example.com", # lier les tokens à cette API
issuer="https://auth.example.com",
options={"require": ["exp", "iat", "aud", "iss"]},
)
except InvalidTokenError:
abort(401)Contrôles supplémentaires :
kid contre une allowlist interne — ne jamais récupérer de clés depuis des URLs dans l'en-têtejwt.decode(token, verify=False) — désactive entièrement la vérification de signature. Courant dans le code de débogage qui finit en production.algorithms=jwt.algorithms.get_default_algorithms().keys() — accepte tous les algorithmes que la bibliothèque supporte, y compris none.kid pour récupérer des clés depuis une URL ou un chemin du système de fichiers — permet SSRF, inclusion de fichier et RCE selon le mécanisme de lookup.exp parce que « le token sera frais de toute façon » — jusqu'à ce qu'un token fuite via des logs ou une compromission d'extension de navigateur.Cette section couvre la taxonomie complète de la mauvaise configuration JWT :
Un JWT est un bearer token compact, URL-safe et stateless composé de trois segments encodés en base64url séparés par des points : en-tête.charge.signature. L'en-tête déclare l'algorithme de signature (alg) et l'identifiant de clé (kid). La charge contient des claims tels que sub (sujet), iss (émetteur), aud (audience), exp (expiration) et des données applicatives personnalisées comme role ou tenant_id. La signature lie l'en-tête et la charge en utilisant l'algorithme choisi et un secret ou une clé privée. Les serveurs valident en recalculant la signature et en la comparant à la valeur intégrée dans le token. Les JWT sont largement utilisés pour les tokens de session, les access tokens OAuth 2.0 et les id_tokens OIDC (RFC 7519, RFC 7515).
Les JWT concentrent trois classes de risque dans une seule chaîne de bearer. Premièrement, la confiance dans la signature est pilotée par le parseur : de nombreuses bibliothèques lisent le champ alg depuis l'en-tête contrôlé par l'attaquant et dispatchent la vérification en conséquence — ce qui permet alg:none, la confusion RS256→HS256 et l'injection kid. Deuxièmement, la force du secret est invisible : les tokens HS256 avec des secrets choisis par les développeurs (moins de 32 octets, mots du dictionnaire ou valeurs par défaut d'environnement comme 'secret' ou 'jwt-secret') tombent face à hashcat mode 16500 en quelques secondes. Troisièmement, la validation est décentralisée : chaque service qui consomme un JWT doit imposer indépendamment les vérifications de signature, d'expiration et d'audience — menant à une dérive par service où un microservice accepte des tokens non signés tandis qu'un autre les rejette.
(1) alg:none — supprimer la signature en déclarant l'absence d'algorithme (CVE-2015-9235, CVE-2026-34950). (2) Secret HMAC faible — brute-force hors ligne des tokens HS256 contre rockyou.txt ou des listes de mots personnalisées. (3) Confusion d'algorithme (RS256→HS256) — soumettre un token signé avec la clé publique du serveur comme secret HMAC. (4) Injection d'en-tête kid — pointer l'identifiant de clé vers un chemin de fichier, une ligne de base de données ou une URL contrôlée par l'attaquant. (5) Injection d'en-tête jwk/x5c/x5u — intégrer la clé publique ou le certificat de l'attaquant dans l'en-tête pour que le serveur lui fasse confiance dès la première utilisation. BreachVex teste les cinq variantes et signale les mappings CVE applicables.
Exécuter trois tests contre l'endpoint d'authentification. (1) Décoder un token valide, changer header.alg à 'none', supprimer le segment de signature (garder le point final), soumettre à nouveau — une réponse 200 indique l'acceptation de alg:none. (2) Changer alg de RS256 à HS256, signer la charge inchangée avec la clé publique RSA du serveur comme secret HMAC — une réponse 200 indique une confusion d'algorithme. (3) Capturer un token HS256 et exécuter hashcat -m 16500 token.txt rockyou.txt — si un secret est cracké en moins d'une heure, le secret est trop faible. Les trois tests doivent échouer sur un serveur correctement configuré.
OWASP A02:2021 (Cryptographic Failures) couvre la suppression de signature et la confusion d'algorithme — défaillances du contrôle cryptographique lui-même (alg:none, RS256→HS256, secrets HMAC faibles). OWASP A07:2021 (Identification and Authentication Failures) couvre les défauts de conception d'authentification de plus haut niveau tels que l'absence de validation d'expiration, l'absence de liaison d'audience, les attaques par rejeu et l'absence de mécanismes de révocation. Un seul système JWT mal configuré viole souvent les deux catégories. Cette page de vue d'ensemble utilise A07:2021 parce qu'elle couvre le sujet plus large de l'authentification JWT ; les variantes spécifiques comme alg:none correspondent plus précisément à A02:2021.
Modèles de menace différents. Les cookies de session côté serveur nécessitent des recherches en base de données mais permettent une révocation immédiate. Les JWT stateless passent à l'échelle horizontalement et éliminent les requêtes en base de données à chaque requête, mais ne peuvent pas être révoqués avant expiration sans une liste de révocation supplémentaire — ce qui réintroduit l'état que les JWT étaient censés éliminer. La réponse pragmatique : des access tokens de courte durée (5-15 minutes) combinés à un pattern de rotation de refresh-token offrent la majeure partie de la scalabilité des JWT tout en gardant une fenêtre de révocation courte. Les JWT à longue durée de vie (heures ou jours) sont un anti-pattern de sécurité.
Cinq contrôles. (1) Allowlist d'algorithmes explicite lors de la vérification : jwt.decode(token, key, algorithms=['RS256']) — ne jamais faire confiance au champ alg de l'en-tête. (2) Clés asymétriques (RS256, ES256, EdDSA) pour les systèmes distribués afin que les clés publiques puissent être partagées sans compromettre la capacité de signature. (3) Secrets HMAC forts (256 bits aléatoires, jamais dérivés de mots de passe) si HS256 est requis. (4) Validation obligatoire de iss, aud et exp, plus nbf (not-before) pour les tokens qui l'incluent. (5) Une stratégie de rotation de clés qui utilise des lookups kid contre une allowlist interne — jamais une URL contrôlée par l'attaquant.