# OWASP JWT: brute-force su 10.000 secret + kid injection

> Audit online di token JWT: alg=none, brute-force su 10.000 HS256 noti, alg confusion, claim mancanti, kid injection, jku/x5u senza pinning. Niente upload.

Strumento gratuito nel browser (nessun upload): https://www.mauriziofonte.it/strumenti/audit-jwt

Incolla un **token JWT** e ottieni in pochi secondi un audit security-first: rilevamento di **alg=none** e permutazioni (`NoNe`, `NULL`, alg numerico), **brute-force** di un secret HS256 contro un dataset di 10.000 secret noti (wallarm/jwt-secrets + SecLists, ricompilato giornalmente), **alg confusion attack** (HS256 firmato con la pubkey RSA del backend) quando fornisci la pubkey, claim RFC 7519 mancanti (`exp`, `iat`, `aud`, `iss`, `jti`), lifetime sospetto, header `kid` con caratteri da injection, `jku` e `x5u` senza pinning, header esoterici (`cty`, `zip`) usati come pivot. L'analisi gira interamente nel browser via WebCrypto: il token non lascia mai la pagina.

## Come usare l'audit

1. **Recupera un JWT** - Tipico flow: cattura il token dalla request HTTP del browser (DevTools > Network > header `Authorization: Bearer...`), oppure da log applicativi, oppure da copia diretta dell'output del tuo identity provider in fase di test. Per analisi assistita: salva il token in un file `.jwt` o `.txt` e usa il dropzone per caricarlo direttamente.
2. **Incolla il token (e opzionalmente la pubkey)** - Tre parti separate da punto: `header.payload.signature`. Il tool valida il formato base64url e fallisce con messaggio diagnostico se uno dei segmenti è malformato. Se vuoi testare l'**alg confusion attack**: apri la sezione "Pubkey opzionale" e incolla la public key RSA del backend (PEM o JWKS). Senza pubkey il check di alg confusion viene saltato; gli altri 15+ check funzionano comunque.
3. **Esegui l'audit** - Bottone **Audit** per il check di default (test contro 1.000 secret più comuni del dataset wallarm/SecLists, completa in <1 secondo). Bottone **Audit profondo** per estendere il brute-force a 10.000 secret (5-10 secondi, tipicamente da usare quando il check rapido non ha trovato match e vuoi escludere il vettore con maggior coverage).
4. **Leggi il report** - Output a tre livelli. **Header e payload decoded** in alto (visibili full-text per ispezione manuale dei claim). **Findings ordinati per severity**: critical, high, medium, low, info. **Score aggregato** 0-100. Per ogni rilievo: descrizione tecnica del vettore, riferimento normativo (CVE, RFC, OWASP), remediation suggerita.
5. **Esporta o stampa** - **Copia report**: text plain per ticket interni o documenti di audit. **Esporta per chatbot**: markdown completo con context del tool, findings dettagliati e prompt template, da incollare in ChatGPT/Gemini/Claude per priorizzare la remediation o stimare gli sforzi di fix. **Stampa** del browser per export PDF (CSS print-ottimizzato che nasconde nav e CTA).

## Vettori di attacco JWT, perché un audit di triage statico ha senso

**Il rischio reale dei JWT sta nella configurazione.** Leggere il payload di un token (cosa fa il [JWT decoder](/strumenti/jwt-decoder)) è la parte facile: chiedersi se quel token è esposto a uno dei pattern noti di compromissione separa un'integrazione production-grade da un incidente in attesa di accadere. Le CVE storiche su librerie JWT diffuse (jsonwebtoken < 8.5, jose < 4, python-jwt, ruby-jwt) sono tutte conseguenza di assunzioni implicite del backend che il tool fa emergere dal solo manifest del token.

**I quattro vettori classici verificati dall'audit.** (1) **alg=none e permutazioni**: librerie storicamente vulnerabili accettavano header `{"alg":"none"}` bypassando la verifica. Il check copre la stringa esatta e tutte le permutazioni di case (`None`, `NONE`, `nULL`, `null`, alg vuoto, alg numerico, alg mancante). (2) **HS256 con weak secret**: HMAC con secret condiviso brute-forzabile offline se il secret è una passphrase umana. L'audit prova un dataset curato di 10.000 secret comuni (wallarm/jwt-secrets + SecLists/scraped-JWT-secrets, ricompilato giornalmente) verificando ogni candidato via WebCrypto HMAC-SHA256 in locale. (3) **alg confusion attack**: il backend è configurato per RS256 ma accetta anche HS256, e usa la public key RSA come secret HMAC. Vettore documentato in CVE storiche (jsonwebtoken pre-8.5). Se fornisci la public key, il tool tenta la verifica HMAC con la pubkey come secret e segnala **critical** se passa: significa che il backend accetterebbe token attacker-controllati firmati con la sua stessa public key (chiave pubblica per definizione). (4) **kid injection / jku-x5u abuse**: header che dichiarano la chiave da usare per la verifica. Se il backend non li sanitizza (kid usato come key path o lookup SQL) o non li pinna a domini fidati (jku/x5u), un attaccante che controlla l'input controlla la chiave.

**Claim mancanti.** RFC 7519 definisce claim opzionali con valore di sicurezza concreto: `exp` (expiration, scadenza temporale), `nbf` (not before, ritardo di attivazione), `iat` (issued at, timestamp di emissione), `jti` (JWT ID, anti-replay), `aud` (audience, mitigazione del confused deputy attack), `iss` (issuer, mitigazione del rogue IdP). Anti-pattern frequente: token senza `exp`, che vivono per sempre senza possibilità di revoca server-side né rotazione automatica; oppure senza `aud`, che il service A può inoltrare al service B che lo accetta perché coperto dallo stesso identity provider, anche se non era destinato a lui.

**Header esoterici.** Due header meno noti che il tool segnala come info: `cty` (content type, abilita nested JWT - un JWT dentro un JWT, vettore di confusione se il backend lo de-wrappa senza riautenticare), `zip` (compression: l'header `{"zip":"DEF"}` abilita la decompressione DEFLATE del payload, ed è stato vettore di attacchi reali via zip bomb su librerie che non limitavano la dimensione output).

**Privacy operativa.** Il token JWT contiene il payload con i claim utente e l'header con la configurazione di firma: in audit reali non vuoi caricarlo su un servizio terzo, anche con TLS. Qui non parte nessuna richiesta di rete con i dati del token. Il dataset di secret (10.000 entries, ~180 KB raw) viene scaricato dal browser una sola volta al primo utilizzo e cachato in `localStorage` per le sessioni successive (invalidazione automatica via SHA-256 sul file pubblico). Audit, parsing del JWT, verifica HMAC, brute-force sui secret, lettura della pubkey: tutto in locale via WebCrypto API.

**Quando il triage statico è il livello giusto.** Casi tipici. (a) Review preliminare di un'API legacy senza accesso al backend: hai catturato un token dal frontend e vuoi capire se la configurazione lato server è solida. (b) Pre-engagement audit per stimare lo sforzo di un eventuale incarico di hardening JWT prima di firmare il preventivo. (c) Screen iniziale prima di un assessment cyber serio: la lista dei high/critical va approfondita con esercizio di exploitability assessment, mappatura dei codepath di verifica nel backend, verifica della whitelist di algoritmi accettati e del pinning JWKS. (d) Self-check periodico per il proprietario dell'API che vuole sapere se l'integrazione è stata fatta a regola d'arte.

**Limite di scope.** L'audit non sostituisce: una review architetturale del flusso completo (rotazione delle chiavi, refresh token rotation, logout server-side con blacklist jti, audience binding, gestione del kid e jku/x5u in produzione, configurazione del provider OIDC); l'analisi statica del codice di verifica nel backend (whitelist algoritmi, leeway sull'`exp`, gestione delle eccezioni); lo studio del threat model specifico dell'applicazione. È un triage e si propone come tale.

## Glossario

- **JWT**: JSON Web Token, RFC 7519. Stringa formato `header.payload.signature`, ogni segmento base64url-encoded. Contiene claim JSON sull'identità o sull'autorizzazione. Standard de facto per API authentication moderne (OAuth2, OpenID Connect).
- **JWS / JWE**: **JWS** (JSON Web Signature, RFC 7515) = JWT firmato, payload in chiaro base64url. **JWE** (JSON Web Encryption, RFC 7516) = JWT cifrato, 5 segmenti invece di 3, payload non leggibile senza chiave. Questo audit gestisce JWS classico (firmato, payload leggibile).
- **alg=none**: Valore dell'header `alg` che indica firma assente. RFC 7518 par. 3.6 lo definisce come opzione, ma OWASP raccomanda di rifiutarlo a livello applicativo per evitare bypass di firma. Vettore di CVE storiche su jsonwebtoken, jose, ruby-jwt e altre librerie.
- **Alg confusion attack**: Vettore in cui il backend, configurato per RS256, accetta anche HS256 e usa la public key RSA come secret HMAC. L'attaccante che ha la pubkey (sempre, perché public) può forgiare token validi. Mitigation: vincolare l'algoritmo nella verifica (es. `algorithms: ['RS256']`).
- **HS256**: HMAC-SHA256, signature simmetrica con secret condiviso fra issuer e verifier. Il secret deve essere casuale e di alta entropia (>= 256 bit, generato con `openssl rand -base64 32`) per resistere al brute force offline.
- **RS256**: RSA-SHA256 con padding PKCS#1 v1.5, signature asimmetrica. Il backend ha la chiave privata per firmare, i client (o altri service) hanno solo la pubblica per verificare. Preferito a HS256 per architetture multi-service. Esiste anche RS384, RS512 con SHA-384/SHA-512.
- **ES256 / ES384 / ES512**: ECDSA con curve P-256, P-384, P-521. Equivalente a RS256 ma con chiavi più corte (256 bit vs 2048-4096) e firme più veloci. Raccomandato per nuovi sistemi. Attenzione alla qualità del PRNG: ECDSA con randomness debole è catastrofico.
- **PS256**: RSASSA-PSS-SHA256, RSA con padding PSS (probabilistic signature scheme). Variante moderna di RS256, raccomandata da NIST. Supportata dalle librerie JWT recenti, meno diffusa storicamente.
- **kid**: Key ID, header opzionale che identifica quale chiave usare per la verifica (utile in JWKS multi-key). Vulnerabilità note se non sanitizzato lato backend (path traversal con `../`, SQL injection nella tabella keys).
- **jku / x5u**: **jku** (JWK Set URL): URL di un JWKS. **x5u** (X.509 URL): URL di un certificato X.509. Header che dichiarano dove trovare la chiave di verifica. Senza pinning lato backend, l'attaccante che controlla la URL controlla la chiave. Da ignorare salvo whitelist esplicita.
- **JWKS**: JSON Web Key Set (RFC 7517). Endpoint pubblico che pubblica le chiavi pubbliche di un identity provider in formato JSON. URL convenzionale: `https://idp.example/.well-known/jwks.json`. Cached con TTL ragionevole, refresh on rotation.
- **exp / nbf / iat**: Claim temporali RFC 7519. `exp` (expiration): timestamp Unix di scadenza, secondi. `nbf` (not before): timestamp prima del quale il token non è valido. `iat` (issued at): timestamp di emissione. Tutti in secondi (non millisecondi: errore comune di porting JS).
- **aud / iss / jti**: Claim semantici RFC 7519. `aud` (audience): identificatore del destinatario, mitigazione del confused deputy. `iss` (issuer): identificatore del provider che ha emesso il token, mitigazione del rogue IdP. `jti` (JWT ID): identificatore univoco, abilita anti-replay via blacklist server-side.
- **OWASP JWT Cheatsheet**: Documento OWASP che codifica le best practice di security per JWT: whitelist algoritmi, secret strength, claim mandatory, expiration, kid handling, jku/x5u pinning, refresh token rotation. Riferimento autoritativo per chi progetta o audita un'integrazione JWT.
- **FriendsOfPHP / SecLists**: **SecLists** (danielmiessler/SecLists su GitHub): collezione open source di wordlist per security testing. La sezione Passwords/scraped-JWT-secrets contiene secret JWT comuni raccolti da CTF, leak, codice pubblico. Sorgente del dataset di brute-force di questo tool insieme a **wallarm/jwt-secrets**.
- **WebCrypto API**: API standard del browser per primitive crittografiche (HMAC, RSA, ECDSA, AES, SHA). Definita in W3C Web Cryptography. Tutta la verifica HMAC e RSA del tool gira via `crypto.subtle`: nessuna libreria JS esterna, performance native, niente dati che lasciano la tab.

## Domande frequenti sull'audit JWT

### Sono il proprietario o il PM di un'API che usa JWT, non sono uno sviluppatore. Mi serve davvero questo tool?

Sì, ed è anzi uno dei target. Per ogni finding ricevi severity (critica, alta, media, bassa, informativa), descrizione del vettore in linguaggio operativo e link al CVE pubblico o alla raccomandazione OWASP. Se il report restituisce anche solo un finding critical, hai un argomento solido da portare al team di sviluppo per chiedere come e quando l'integrazione verrà sistemata. Tipico segnale d'allarme: secret HS256 trovato nel dataset wallarm = secret fra i 10.000 più diffusi sul web = patch necessaria entro le 24 ore.

### Dove cattomi un JWT della mia applicazione per testarlo?

Tre vie tipiche. (1) DevTools del browser: tab Network, filtra per le request all'API, cerca l'header `Authorization: Bearer ...`, copia tutto dopo "Bearer ". (2) Storage del client: spesso il JWT è in `localStorage` (key tipo `access_token`, `idToken`) o in cookie. (3) Sviluppatore: chiedi al team di prodottu un token di test (non di produzione su utente reale: meglio un account di staging). Il tool gira solo in locale, il token non lascia il browser, ma per igiene non incollare mai token reali in tool che non controlli.

### Il tool decodifica anche la firma? Posso vederla?

Il terzo segmento (signature) viene mostrato come stringa base64url, non decodato. Le firme HMAC, RSA ed ECDSA sono byte raw e leggerle non aggiunge informazioni utili. Per verificarla serve la chiave: per HS256 usa il [JWT decoder](/strumenti/jwt-decoder) con secret nel campo dedicato; per RS256/ES256 puoi incollare la pubkey nel campo opzionale di questo audit, e se il token è HS256-spoofato con la pubkey come secret il check di alg confusion lo segnala.

### Cosa succede se il tool trova un secret debole? Cosa significa praticamente?

Significa che chiunque conosca o indovini quel secret può forgiare token validi a piacere, impersonando qualunque utente. Pattern di mitigation immediata: (1) ruotare il secret a una stringa casuale crypto-safe (`openssl rand -base64 32`); (2) invalidare tutti i token in circolazione (forza re-login a tutti gli utenti, spiacevole ma necessario); (3) verificare i log applicativi delle ultime settimane per attività sospette su account privilegiati; (4) ripensare l'architettura: se HS256 è davvero la scelta giusta o se RS256 sarebbe più appropriato per l'arch.

### Cos'è l'alg confusion attack? Il check è affidabile?

L'attacco sfrutta backend mal configurati che, ricevendo un token HS256, lo verificano con la stessa chiave usata per RS256, applicando l'algoritmo dichiarato nell'header del token (HS256) invece che quello atteso dall'architettura (RS256). Risultato: il backend tratta la public key RSA come secret HMAC, e siccome la public key è pubblica, l'attaccante può forgiare token validi. Il check del tool è affidabile al 100%: prende la pubkey che fornisci, la usa come secret HMAC contro la firma del token, se la verifica passa il vettore è confermato. Senza pubkey, il check viene saltato.

### Audit profondo (10.000 secret) vs Audit normale (1.000): quando serve davvero?

Il tier fast (1.000 secret) copre il 95% dei casi reali: i secret comuni ("secret", "password", "changeme", combinazioni con il nome del progetto) sono tutti lì in cima. Il tier full (1.001-10.000) aggiunge la coda dei secret meno frequenti ma documentati in CTF, leak GitHub, pastebin storici. Quando usare il deep: (a) il fast non ha trovato match e vuoi escludere il vettore con coverage estesa prima di chiudere il triage; (b) audit di alto rigore prima di pubblicare un report ufficiale; (c) il backend è in scope NIS2 e devi documentare due diligence.

### Token scaduto: è un finding bloccante?

No, è classificato INFO. Un token scaduto NON è una vulnerabilità di sicurezza (anzi, l'expiration è una mitigazione attiva). Il tool segnala lo stato per evitare false positive nel report (un audit che dicesse "manca exp" su token che invece ha exp ma è scaduto sarebbe un bug).

### Lifetime > 30 giorni è realmente un problema?

Dipende dal use case. Per token di sessione utente: sì, 30 giorni è troppo, refresh token rotation con access token short-lived (15-60 min) è la pratica raccomandata. Per token di servizio (API key formato JWT): 30+ giorni può essere accettabile se ruotati e revocabili lato backend. Il tool segnala HIGH come default conservativo: valuta il context e ignora se il use case lo giustifica.

### kid injection: come si exploita davvero?

Esempi reali di vettori. (1) `kid: "../../../dev/null"` -> il backend cerca la chiave nel filesystem -> trova file vuoto -> verifica HMAC con secret = stringa vuota -> forgery riuscita. (2) `kid: "key1' UNION SELECT 'attacker'-- "` -> il backend fa SQL su tabella keys -> SQLi -> chiave attacker controllata. Mitigation: trattare `kid` come input untrusted, whitelist alfanumerica, lookup esatto in mappa interna, mai parametro di query non parametrizzata.

### Perché jku/x5u sono dichiarati ad alto rischio?

Permettono al token di dichiarare la URL della chiave pubblica con cui verificarsi. Se il backend la usa as-is (senza pinning a domini fidati), un attaccante che controlla la URL controlla la chiave -> firme valide a piacere. Mitigation: pinning whitelist di hostname autorizzati a livello backend, oppure ignorare `jku`/`x5u` e usare solo JWKS endpoint hardcoded.

### Il tool integra una libreria PHP/JS esterna a runtime?

No. Parsing JWT (base64url + JSON.parse), brute-force HS256 (WebCrypto HMAC.verify), import della pubkey PEM (WebCrypto importKey con SubjectPublicKeyInfo): tutto API native del browser. Zero dipendenze, ~16 KB di JavaScript totali. Il dataset secrets è statico, ricompilato dal cron PHP server-side.

### Differenza fra questo audit e il JWT decoder del sito?

Il [JWT decoder](/strumenti/jwt-decoder) è un visualizzatore: decodifica header e payload, spiega ogni claim, calcola il countdown a exp, opzionalmente verifica la firma HS256 se gli dai il secret. Questo audit assume la firma valida (o la verifica al volo per il check di alg confusion) e si concentra sui rischi di configurazione: vettori di forgery, weak secret, claim mancanti, header pericolosi. Sono complementari: decoder per capire cosa contiene un token, audit per capire se è esposto.

### Privacy: il token che incollo viene salvato o tracciato?

No. Il tool gira interamente nel browser. Le uniche richieste di rete sono il GET dei file dataset secrets (identici per tutti gli utenti, completamente anonimi) e il GET dell'`index.json`. Il token incollato non viene mai inviato in alcuna richiesta, non viene salvato in `localStorage`, non viene tracciato in analytics. Verificabile aprendo il pannello Network di DevTools durante l'audit: zero richieste outgoing con i dati del token.

### Posso fare audit di un Refresh Token o di un ID Token OpenID Connect?

Sì, sintatticamente sono entrambi JWT validi e l'audit funziona uguale. Punti rilevanti per refresh token: (a) `exp` deve essere ragionevolmente lungo (giorni-mesi tipico) ma con jti per anti-replay; (b) `aud` ristretto all'endpoint di refresh; (c) idealmente HS256 sostituito da RS256 in arch multi-service. Per ID token OIDC: `iss` deve matchare l'URL del provider OIDC, `aud` deve contenere il client_id, `nonce` deve matchare quello inviato nella authorization request.

### Posso fare un audit completo del flusso, non del singolo token?

Questo audit segnala i rischi visibili dal singolo token. Per esaminare il flusso completo (rotazione delle chiavi, refresh token rotation, logout server-side, anti-replay con jti, audience binding, gestione di kid e jku/x5u in produzione, configurazione del provider OIDC) serve una review architetturale che attraversi codice, configurazione del provider e infrastruttura. Vai al CTA in fondo alla pagina.

---
Uno strumento di Maurizio Fonte, senior backend engineer. Tutti gli strumenti: https://www.mauriziofonte.it/strumenti/
