Note : les techniques présentées ici sont destinées à un usage offensif dans le cadre de tests d'intrusion autorisés. Toute utilisation sans mandat écrit est illégale.
Comprendre JWT en 30 secondes
Un token JWT est composé de trois parties encodées en base64url, séparées par des points :
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ3aWVuZXIifQ.SIGNATURE
HEADER PAYLOAD SIGNATURE
Le header contient l'algorithme de signature. Le payload contient les claims (identité, rôle, expiration). La signature garantit l'intégrité de l'ensemble.
Deux familles d'algorithmes coexistent :
- RS256 (asymétrique) le serveur signe avec sa clé privée, vérifie avec sa clé publique. La clé publique peut être exposée librement, c'est le principe même de la cryptographie asymétrique. Elle est souvent disponible sur un endpoint standard :
/jwks.json. - HS256 (symétrique) le serveur signe ET vérifie avec le même secret. Ce secret ne doit jamais être exposé. Si un attaquant le connaît, il peut forger n'importe quel token.
L'attaque par confusion d'algorithme exploite précisément la frontière entre ces deux mondes.
Le bug Algorithm Confusion expliqué
Voici le code vulnérable type (pseudo-code Node.js) :
// ❌ Vulnérable
const decoded = jwt.verify(token, publicKey);
// La lib lit `alg` dans le header du token.
// Si alg=HS256 → utilise publicKey comme secret HMAC.
// La signature est valide → token accepté.
// ✅ Correct
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
// L'algorithme est imposé côté serveur, le header est ignoré.
Quand le serveur ne fixe pas l'algorithme attendu, la librairie JWT fait confiance au champ alg présent dans le header du token. Un attaquant peut donc soumettre un token avec alg: HS256. La librairie va alors utiliser la clé publique RSA comme secret HMAC pour vérifier la signature.
Or la clé publique est... publique. Elle est exposée via /jwks.json. L'attaquant la connaît, peut signer avec elle en HS256, et le serveur accepte la signature comme valide.
C'est ça l'algorithm confusion : transformer une clé publique en secret symétrique exploitable.
Librairies historiquement vulnérables :
node-jsonwebtoken< 9.0python-jose(certaines versions)PyJWT< 2.4.0
Ce pattern de confiance aveugle dans les métadonnées envoyées par le client est le même qu'on retrouve dans d'autres classes de bugs web. Voir nos articles sur le HTTP Request Smuggling (désynchronisation frontend/backend) et sur les chemins d'attaque Active Directory (abus de délégations et de tickets) le fil rouge, c'est toujours une frontière de confiance mal placée.
Lab PortSwigger Walkthrough
Lab utilisé : « JWT authentication bypass via algorithm confusion » (niveau Expert).
Phase 1 Reconnaissance : récupérer la clé publique
Après connexion avec les credentials wiener:peter, on intercepte le GET /my-account dans Burp et on l'envoie en Repeater. On modifie le path en /admin : réponse 401, accès refusé.
On navigue ensuite vers /jwks.json. Le serveur expose sa clé publique RSA au format JWK :
{
"keys": [{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "d37ffab5-3743-4e16-8f1d-92ec5b888a66",
"alg": "RS256",
"n": "sF24jYER8zDoJOWgNLQ96fHEOAJ89FVsmXjV8dIeqLUr..."
}]
}
En pentest réel, cet endpoint est fréquemment exposé sans restriction sur des applications qui utilisent des Identity Providers (Keycloak, Auth0, AWS Cognito). On croise aussi très souvent /.well-known/jwks.json à ajouter systématiquement à tes wordlists de recon.
Phase 2 Préparer la clé malicieuse dans Burp
Étape 2.1 Importer la clé publique RSA dans JWT Editor. Dans Burp → onglet JWT Editor Keys → New RSA Key. On sélectionne le format JWK et on colle l'objet récupéré depuis /jwks.json (l'objet seul, sans les crochets du tableau keys).
Étape 2.2 Exporter la clé publique en PEM puis l'encoder en Base64. Clic droit sur la clé → Copy Public Key as PEM. On obtient un bloc PEM classique :
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsF24jYER8z...
-----END PUBLIC KEY-----
On va dans l'onglet Decoder de Burp, on colle le PEM, et on l'encode en Base64. On copie la string résultante.
Étape 2.3 Créer la clé symétrique HS256. JWT Editor Keys → New Symmetric Key → Generate. Un JWK est généré avec un champ k (le secret). On remplace la valeur de k par la string Base64 du PEM, puis on sauvegarde. La clé publique RSA est maintenant utilisable comme secret symétrique HS256.
Phase 3 Forger et envoyer le token
On retourne sur la requête GET /admin en Repeater. L'extension JWT Editor ajoute un onglet JSON Web Token qui affiche le token décodé. On effectue deux modifications :
- Dans le header :
"alg": "RS256"→"alg": "HS256" - Dans le payload :
"sub": "wiener"→"sub": "administrator"
En bas de l'onglet, on clique Sign, on sélectionne la clé symétrique créée précédemment, et on s'assure que l'option Don't modify header est cochée (pour conserver notre modification de alg). On envoie la requête.
Finalisation Supprimer carlos
Dans le HTML de la réponse, on identifie le lien de suppression :
/admin/delete?username=carlos
On modifie le path dans Repeater et on envoie. Lab résolu.
Impact terrain
En contexte de pentest réel, cette vulnérabilité mène directement à une escalade de privilèges complète. Un compte utilisateur standard devient administrateur sans bruteforce, sans injection, sans interaction côté serveur autre qu'une requête HTTP modifiée.
Ce qu'on cherche en audit :
- Endpoint
/jwks.jsonou/.well-known/jwks.jsonaccessible sans authentification. - Header
algaccepté tel quel par le serveur (test : soumettre un token HS256 sur une app RS256). - Version des librairies JWT dans les dépendances (
package.json,requirements.txt,pom.xml). - Réponse différente entre un token mal signé et un token avec algorithme modifié indique que le serveur traite l'algo du header.
Critique (CVSS 9.1+) selon le contexte. Impact confidentialité et intégrité maximaux, exploitation sans prérequis technique élevé une fois la clé publique récupérée. C'est typiquement le genre de finding qu'on remonte dans un rapport d'audit de cybersécurité avec un plan de remédiation immédiat.
Remédiation
La correction est simple et doit être appliquée côté serveur, indépendamment du framework utilisé.
Node.js jsonwebtoken
// ❌ Vulnérable
jwt.verify(token, publicKey);
// ✅ Corrigé
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
Python PyJWT
# ❌ Vulnérable
jwt.decode(token, public_key)
# ✅ Corrigé
jwt.decode(token, public_key, algorithms=["RS256"])
Règles générales
- Ne jamais faire confiance au champ
algdu header JWT pour choisir l'algorithme de vérification. - Définir une whitelist d'algorithmes acceptés, explicitement, dans le code serveur.
- Mettre à jour les librairies JWT :
jsonwebtoken >= 9.0,PyJWT >= 2.4.0. - Si le JWKS n'a pas besoin d'être public, le restreindre aux origines légitimes ou le supprimer.
- En architecture microservices, s'assurer que chaque service qui valide des tokens impose également l'algorithme.
Sur un périmètre web avec authentification JWT, ce test fait partie d'une méthodologie d'audit offensif qu'on applique en mission. C'est le même état d'esprit que celui décrit dans notre retour d'expérience sur la certification CPTS, et qu'on met en pratique sur des cas concrets comme la race condition sur un moteur de réservation hôtelier.
Conclusion
L'algorithm confusion est un exemple parfait de vulnérabilité qui ne repose pas sur une faiblesse cryptographique, mais sur une erreur de conception dans l'implémentation. La cryptographie RS256 est solide. C'est la confiance aveugle accordée au header du token qui ouvre la brèche.
Ce vecteur est discret, peu bruyant, et souvent absent des checklists d'audit basiques. C'est précisément ce type d'angle qu'on creuse chez HackHeart : les vulnérabilités qui existent en production, pas seulement dans les CTF.
Ressources
- PortSwigger Web Security Academy JWT algorithm confusion
- RFC 7519 JSON Web Token (JWT)
- RFC 7517 JSON Web Key (JWK)
Audit ciblé JWT en 1 à 2 jours : alg confusion, none, key disclosure, expiration, rotation. À partir de 1 500 € HT.