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 :

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 :

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..."
  }]
}
// Note terrain

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.

GET /jwks.json dans le navigateur

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 KeysNew 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).

GET /jwks.json dans le navigateur Decoder Burp avec le PEM encodé en Base64

É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.

Decoder Burp avec le PEM encodé en Base64

Étape 2.3 Créer la clé symétrique HS256. JWT Editor KeysNew Symmetric KeyGenerate. 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.

Decoder Burp avec le PEM encodé en Base64

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 :

  1. Dans le header : "alg": "RS256""alg": "HS256"
  2. 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.

Decoder Burp avec le PEM encodé en Base64 Decoder Burp avec le PEM encodé en Base64

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.

Decoder Burp avec le PEM encodé en Base64

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 :

// Criticité en rapport de pentest

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

// Pour aller plus loin

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

Votre auth JWT a-t-elle déjà été testée ?

Audit ciblé JWT en 1 à 2 jours : alg confusion, none, key disclosure, expiration, rotation. À partir de 1 500 € HT.