A maggio 2015 l’IETF ha pubblicato RFC 7519, che definisce il formato dei JSON Web Token (JWT). Insieme a RFC 7515–7518, usciti nello stesso mese, chiude il lavoro del gruppo JOSE (JavaScript Object Signing and Encryption) e dà un riferimento normativo a un formato che le librerie implementavano già da tempo seguendo gli Internet-Draft.
Contesto
Un JWT trasporta un insieme di affermazioni (i claim) in forma compatta, adatta a stare dentro una URL. Le tre parti — intestazione, payload, firma — sono codificate in base64url e separate da un punto:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE0MzQwMzM2MDB9.<firma>
L’intestazione dichiara l’algoritmo (alg) e il tipo (typ). Il payload contiene i claim. La firma copre i primi due segmenti concatenati. Un JWT firmato è una struttura JWS (RFC 7515); un JWT cifrato è una struttura JWE (RFC 7516). Il primo protegge l’integrità ma lascia il payload in chiaro — chiunque lo decodifica in base64url — mentre il secondo protegge anche la riservatezza.
La famiglia si compone così:
RFC 7515— JWS, firme e MACRFC 7516— JWE, cifraturaRFC 7517— JWK, rappresentazione delle chiaviRFC 7518— JWA, algoritmi e identificatoriRFC 7519— JWT, il contenitore di claim
Gli editor di RFC 7519 sono Michael B. Jones, John Bradley e Nat Sakimura.
Claim registrati
La sezione 4.1 dello standard riserva un piccolo insieme di nomi con semantica fissata. Sono tutti opzionali, ma il loro significato è normato:
iss— chi ha emesso il tokensub— il soggetto a cui si riferisceaud— il destinatario previstoexp— istante di scadenza (secondi da epoch)nbf— non valido prima di questo istanteiat— istante di emissionejti— identificatore univoco del token
Averli nello standard, e non in convenzioni che cambiavano da libreria a libreria, è il vantaggio più concreto: exp vuol dire la stessa cosa ovunque, e un validatore conforme deve rifiutare un token scaduto. Tutti gli altri campi del payload restano liberi.
Algoritmi
RFC 7518 definisce gli identificatori di algoritmo usati nell’intestazione. I tre gruppi principali per la firma:
HS256/HS384/HS512— HMAC con chiave simmetrica condivisaRS256/RS384/RS512— RSA con firma asimmetricaES256/ES384/ES512— ECDSA su curve ellittichePS256/PS384/PS512— RSA-PSS
E poi none, definito per i JWT non protetti: nessuna firma, segmento finale vuoto. Serve nei casi in cui l’integrità è garantita da un altro livello del trasporto. È anche il primo errore di chi implementa, come vedremo.
Il punto critico
Lo standard descrive un formato solido; le sue implementazioni, a inizio 2015, non lo erano. Il 31 marzo Tim McLean ha pubblicato una rassegna del codice delle librerie più diffuse — node-jsonwebtoken, pyjwt, php-jwt, namshi/jose e altre — documentando due classi di difetto presenti nei comportamenti predefiniti.
La prima riguarda alg: none. Diverse librerie accettavano come valido un token con intestazione {"alg":"none"} e segmento di firma vuoto, e la routine di verifica restituiva vero. Chiunque poteva quindi costruire un token con il payload che voleva — sub arbitrario, exp lontano nel tempo — e farlo passare per autentico. La causa è una scelta dell’API: la funzione verify leggeva l’algoritmo dall’intestazione del token, cioè da un campo che controlla l’attaccante.
La seconda è la confusione tra algoritmo asimmetrico e simmetrico. Una libreria che gestiva sia HMAC sia RSA verificava un token HS256 con qualunque chiave le venisse passata. Se l’applicazione si aspettava RS256 e passava alla verify la propria chiave pubblica RSA, un attaccante poteva firmare un token HS256 usando quella stessa chiave pubblica come segreto HMAC. La chiave pubblica è pubblica per definizione: l’attaccante la possiede. La verifica andava a buon fine perché la libreria trattava i byte della chiave RSA come segreto simmetrico.
I due difetti hanno la stessa radice: lasciare che sia il token a dichiarare come va verificato. La correzione è spostare la decisione sull’applicazione e toglierla al token. La verify deve ricevere l’elenco degli algoritmi attesi e rifiutare tutto il resto, none compreso.
Implicazioni
Per chi emette e verifica JWT da qui in avanti:
- Vincolare la verifica a un algoritmo, o a una lista chiusa, deciso dal codice e mai dall’intestazione del token.
- Rifiutare
nonein modo esplicito, salvo che il flusso lo richieda per ragioni precise. - Validare sempre
expe, se presente,nbf; controllareisseaudrispetto ai valori attesi. - Tenere
expbreve. Un JWT firmato non si revoca prima della scadenza senza reintrodurre uno stato lato server (lista di revoca, identificatorijtiinvalidati): la natura stateless è il vantaggio del formato ed è anche il suo limite operativo. - Non scambiare la firma per riservatezza: un JWS si legge. Dati personali nel payload chiedono un JWE, oppure restano fuori dal token.
Limiti
RFC 7519 standardizza il contenitore, non l’uso. Quali claim rendere obbligatori per il proprio caso, come ruotare le chiavi, dove conservare il token nel client — un JWT in localStorage è esposto a script ostili in modo diverso da un cookie di sessione — restano decisioni dell’applicazione. Lo standard rende interoperabile il formato e dà un vocabolario comune; le due classi di bug del marzo 2015 mostrano che la parte fragile sta altrove, nell’API di verifica che le librerie mettono a disposizione.
- https://www.rfc-editor.org/rfc/rfc7519.html
- https://www.rfc-editor.org/rfc/rfc7515.html
- https://www.rfc-editor.org/rfc/rfc7518.html
- https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
- https://www.noze.it/insights/jwt-rfc-7519/
Immagine di copertina: Diagramma schematico della firma digitale: a sinistra il mittente calcola l’hash del messaggio e lo firma con la chiave privata; a… — diagramma di Acdx, CC BY-SA 3.0 — https://commons.wikimedia.org/wiki/File:Digital_Signature_diagram.svg