Casser les secrets JWT HS256 en force brute avec hashcat
Un token HS256 contient tout ce qu'il faut pour vérifier un secret deviné hors ligne. Comment les clés HMAC faibles tombent sous hashcat -m 16500, puis forger des tokens.
Un JSON Web Token, ce sont trois segments base64url reliés par des points : header.payload.signature. L'en-tête nomme l'algorithme, la charge utile porte les revendications (qui vous êtes, ce que vous pouvez faire, quand le token expire), et la signature est ce qui vous empêche de modifier la charge utile en toute impunité. Quand l'en-tête indique alg: HS256, cette signature est un HMAC-SHA256 calculé sur header.payload avec un unique secret partagé. Le serveur signe avec, le serveur vérifie avec. Voyez le décryptage du format JWT pour comprendre comment les pièces s'assemblent.
Cette conception symétrique est la vulnérabilité tout entière.
Pourquoi c'est une attaque hors ligne
Avec une signature asymétrique, la vérification réclame la clé publique et la falsification réclame la clé privée, que l'attaquant n'a pas. HMAC est symétrique. La clé qui vérifie est la clé qui signe. Et le token vous livre tout : le message signé est là, sous forme de header.payload, le tag est le troisième segment. Un attaquant qui capture un seul token valide peut donc rester hors ligne et tester des secrets à la vitesse du GPU, recalculant le HMAC pour chaque candidat et le comparant à la signature capturée. Pas de point d'authentification, pas de verrouillage, pas de limite de débit, pas de journal. Le token est un oracle autonome.
C'est une situation radicalement différente du cassage d'une empreinte de mot de passe issue d'une fuite de base de données, mais l'économie rime : HMAC-SHA256 est une primitive rapide, donc le débit est énorme et un secret faible ne survit pas à la première liste de mots.
Cassage avec hashcat -m 16500
Le mode hashcat 16500, c'est JWT. L'empreinte qu'on lui donne est le token entier, sans modification.
# token.txt contient une ligne : le header.payload.signature complet
hashcat -m 16500 token.txt /usr/share/wordlists/rockyou.txt
Un exemple concret. Ce token est signé avec le secret secret :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Placez cette unique ligne dans token.txt et lancez la commande ci-dessus. Le cassage est instantané, car secret figure tout en haut de rockyou. Quand le dictionnaire s'épuise, montez en puissance avec des règles ou un masque :
hashcat -m 16500 token.txt rockyou.txt -r /usr/share/hashcat/rules/best64.rule
hashcat -m 16500 -a 3 token.txt ?a?a?a?a?a?a?a?a
Les secrets faibles sont le vrai risque
Le mode de défaillance intéressant n'a rien d'exotique. C'est qu'un nombre considérable de services signent des tokens HS256 avec un secret tapé à la main ou collé depuis un tutoriel. La chaîne littérale secret. La valeur d'exemple d'un framework restée dans la configuration. your-256-bit-secret, qui est l'espace réservé du débogueur jwt.io et que des gens déploient en production. changeme, le nom de l'application, le nom de l'entreprise. Tout cela tombe dans les premières secondes d'une exécution, parce que ces valeurs sont déjà dans les listes de mots, ou à une règle d'un mot qui s'y trouve.
Une fois le secret récupéré, vous ne vous contentez pas de lire les tokens. Vous en fabriquez. La signature cesse d'être un obstacle puisque vous détenez la clé de signature. Changez la charge utile en "role": "admin", mettez "sub" sur un autre utilisateur, repoussez la revendication exp de plusieurs années, resignez avec le secret cassé, et le serveur l'accepte comme authentique. C'est de l'élévation de privilèges et de la prise de contrôle de compte à partir d'une seule chaîne faible. Le cassage est l'attaque tout entière.
À ne pas confondre avec alg:none ou RS256 vers HS256
Deux autres attaques JWT sont mentionnées dans la foulée, et il vaut la peine de les tenir à l'écart : ce ne sont pas du cassage et elles ne dépendent pas d'un secret faible.
L'attaque alg: none exploite les serveurs qui honorent un en-tête prétendant que le token n'est pas signé. On retire la signature, on met alg à none, et un vérificateur défaillant accepte. L'attaque de confusion RS256 vers HS256 exploite les serveurs qui choisissent l'algorithme de vérification d'après l'en-tête contrôlé par l'attaquant : on prend la clé publique RSA d'un service, qui n'est pas secrète, et on l'utilise comme secret HMAC pour signer un token HS256 falsifié, poussant le serveur à vérifier une signature symétrique avec du matériel qu'il a publié. Ces deux cas sont des failles d'implémentation dans la logique de vérification. Casser le secret HMAC est un problème de force de la clé. Cause profonde différente, correctif différent. Ne les amalgamez pas.
Défendre HS256
Si vous devez recourir à HMAC, le secret doit être long et aléatoire : 32 octets ou plus issus d'un CSPRNG, stockés dans un gestionnaire de secrets, jamais dans le code source. Un secret de cette taille vous met hors de portée des listes de mots et de tout masque que vous pourriez terminer cette décennie. Faites-le tourner, en acceptant que la rotation invalide les tokens en cours : prévoyez donc un schéma d'identifiant de clé (kid) pour basculer proprement.
Mieux encore, abandonnez la signature symétrique pour tout ce qui franchit une frontière de confiance. RS256 ou ES256 séparent la signature de la vérification : la clé privée signe et ne quitte jamais l'émetteur, les services vérifient avec la clé publique, et une clé publique divulguée ne forge rien. Cela élimine toute la catégorie des constats « nous avons cassé votre secret partagé ». HS256 convient à l'intérieur d'un seul service qui possède les deux bouts. Dès qu'un vérificateur hors de votre contrôle doit valider vos tokens, passez à l'asymétrique.