Audit automatizzato di implementazioni JWT e OAuth con LLM: il catalogo delle vulnerabilità ricorrenti

Audit automatizzato di implementazioni JWT e OAuth con LLM: il catalogo delle vulnerabilità ricorrenti

Nella mia sandbox di audit sistematico tengo aperto dal settembre 2025 un corpus di oltre 120 implementazioni PHP di autenticazione token-based - mix di JWT stateless, OAuth 2.0 authorization code flow, OAuth 2.1 PKCE, implementazioni custom basate su HMAC di sessione - provenienti da codebase open source, esempi di tutorial, stack didattici su piattaforme di corsi, e miei progetti interni costruiti a scopo di stress test. A fine marzo 2026 ho completato un ciclo di analisi con un classificatore LLM che ho costruito per individuare pattern di vulnerabilità ricorrenti, e i numeri sono brutali: il 94% del campione conteneva almeno una implementazione sub-ottimale rispetto alla RFC 8725 (JSON Web Token Best Current Practices) del IETF, il 67% aveva almeno una vulnerabilità critica sfruttabile, e il 23% era classificabile direttamente come token forgery o account takeover su un proof of concept funzionante in ambiente controllato. JWT e OAuth sono standard semplici nella teoria e catastrofici nell'implementazione, e la regolarità con cui i pattern pericolosi si ripresentano giustifica largamente l'automazione di una parte dell'audit con un classificatore LLM-based - a patto che sia progettato come first-pass filter e non come oracle autonomo.

Perché JWT e OAuth continuano a essere implementati male nel 2026?

La specifica JWT (RFC 7519) è pubblicata dal 2015, la RFC 8725 che raccoglie le Best Current Practices dal 2020, il framework OAuth 2.0 (RFC 6749) dal 2012, OAuth 2.1 draft consolidato da anni. Dieci anni di documentazione pubblica non sono bastati a eliminare le implementazioni pericolose, e la ragione ingegneristica è strutturale: entrambi gli standard sono framework estensibili che permettono molte configurazioni diverse, molte delle quali sono silenziosamente insicure se non accompagnate da una validazione strict. JWT accetta in principio sedici algoritmi di firma diversi inclusi none e algoritmi weak come HS256 con chiavi corte; OAuth 2.0 permette redirect URI con wildcard, implicit flow con token in URL, refresh token senza rotation. Sono tutte opzioni legali, quasi tutte scelte di default sbagliate.

Il secondo fattore è culturale. La maggior parte dei tutorial PHP di autenticazione mostra il pattern più semplice possibile - tipicamente HS256 con chiave statica in .env, decodifica via firebase/php-jwt::decode() con secret globale, refresh basato su timestamp - senza segnalare le caveat operative. Uno sviluppatore che implementa auth leggendo tre tutorial e una guida a Stack Overflow produce con altissima probabilità almeno una di queste vulnerabilità, e il rischio è che nessuno se ne accorga fino al primo pen test o al primo incidente. Il classificatore LLM nella mia pipeline nasce per coprire questo gap: non sostituisce la competenza di un auditor offensive, ma porta in evidenza i pattern più ricorrenti prima che arrivino in produzione.

Le cinque vulnerabilità ricorrenti che il classificatore LLM riconosce

Prima: algorithm confusion e alg=none

Su 120 implementazioni analizzate, 31 presentavano almeno una forma di vulnerabilità legata alla negoziazione dell'algoritmo di firma. La forma più grave è l'accettazione silenziosa di alg: none nel header JWT - un attaccante che intercetta un token valido può modificarlo per escludere la firma, e se il codice non valida esplicitamente l'algoritmo atteso il token modificato viene accettato come legittimo. La seconda forma è la algorithm confusion tra HS256 (simmetrica) e RS256 (asimmetrica): se l'endpoint di verifica legge la chiave pubblica RSA dalla configurazione ma usa firebase/php-jwt::decode($jwt, $publicKey) senza passare l'array degli algoritmi permessi, un attaccante può forgiare token firmati con HS256 usando la public key come shared secret, e il decoder li accetta come validi. La terza forma è l'uso di HS256 con chiavi di entropia insufficiente - tipicamente secret di 16-24 caratteri da environment variable non-randomizzata, vulnerabili a brute force offline.

Il classificatore LLM riconosce questi pattern via grep semantico: cerca chiamate a decode() o verify() senza secondo parametro con whitelist di algoritmi, controlla che le chiavi siano generate da source di entropia adeguata (random_bytes, openssl_random_pseudo_bytes), rileva le concatenation di chiavi da sorgenti multiple che tipicamente riducono l'entropia effettiva. Il signal-to-noise su questa classe è alto: su 31 positivi, 28 erano veri positivi sfruttabili, 3 erano falsi positivi dove il codice passava comunque da un gateway upstream che validava correttamente.

Seconda: refresh token con entropia debole o senza rotation

Il refresh token è il long-lived credential che permette di rinnovare l'access token scaduto. Su 120 implementazioni, 44 avevano problemi su questa classe. I pattern più ricorrenti: refresh token generati come UUID v4 (entropia OK per identificazione ma spoofable se il generatore è predittibile); refresh token stringhe casuali sotto i 32 byte (sopra brute force con wordlist); refresh token che non vengono ruotati a ogni uso - un refresh accettato dovrebbe sempre generare un nuovo refresh e invalidare il precedente, ma in molte implementazioni lo stesso refresh resta valido indefinitamente; refresh token salvati in storage senza hash - se il database viene compromesso, l'attaccante ha accesso diretto a long-lived credentials di tutti gli utenti.

Il classificatore cerca pattern come Str::uuid() o uniqid() per generazione refresh (weak), verifica presenza di rotation logic nell'endpoint /token/refresh, controlla che i refresh salvati siano hashed (password_hash o bcrypt), verifica l'esistenza di una revocation list per gestire logout esplicito.

Terza: storage client-side insicuro

38 su 120 implementazioni esponevano il token a rischio di cross-site scripting (XSS) attraverso lo storage client-side scelto. Il pattern più comune è salvare l'access token in localStorage - accessibile da qualunque JavaScript in-page, quindi se c'è una singola XSS il token è esfiltrabile. Il pattern meno comune ma peggiore è salvare il refresh token in localStorage - stessa vulnerabilità ma impatto esteso. La mitigazione è l'uso di cookie HttpOnly, Secure, SameSite=Strict per il refresh token, e se l'architettura permette per l'access token breve; dove si deve comunque esporre token a JavaScript, si minimizza la TTL (massimo 15 minuti) e si accetta il rischio residuo come trade-off esplicito.

Il classificatore fa grep nel JavaScript frontend per localStorage.setItem.*token, sessionStorage.setItem.*token, e verifica nel backend che i cookie di auth abbiano i flag corretti. Un false positive comune qui è il salvataggio in localStorage di un token di bassa sensibilità (tipo feature flag temporaneo); la validazione manuale serve.

Quarta: revocation list mancante o inefficace

Un JWT firmato correttamente è valido fino alla sua scadenza naturale. Se l'utente fa logout, se l'amministratore sospende un account, se viene compromesso un device - senza una revocation list il token emesso resta valido. 72 implementazioni su 120 non avevano meccanismo di revocation per i token JWT di breve durata, accettando silenziosamente che un logout lato server fosse solo cosmetico. Per token di durata 15 minuti il rischio è contenuto; per token di durata 24 ore (comune nei tutorial) il rischio è serio.

Le due strategie compatibili con JWT sono (1) blacklist in Redis con TTL uguale alla durata del token, verificata a ogni richiesta; (2) token short-lived obbligatorio con refresh che passa da revocation check sul DB. Il classificatore verifica la presenza dell'una o dell'altra e alza warning se entrambe mancano.

Quinta: OAuth redirect URI con validazione permissiva

Su 47 implementazioni OAuth nel campione, 19 avevano redirect URI validation permissive - accettavano pattern con wildcard, verifica solo del dominio base senza controllo del path, o confronto case-insensitive che permette homograph attack. La conseguenza è authorization code interception: un attaccante induce la vittima a iniziare un flusso OAuth legittimo, ma il redirect finisce a un endpoint che controlla l'attaccante, che ruba il code ed esegue il token exchange a suo nome.

Il classificatore cerca implementazioni che confrontano redirect URI con str_starts_with, fnmatch, regex custom invece di equality stretta. La mitigazione corretta è la whitelist esplicita di redirect URI esatti registrati al momento della creazione del client OAuth.

Se stai implementando o facendo auditing di sistemi di autenticazione in progetti PHP e vuoi metodologia applicata sui pattern di vulnerabilità più diffusi, nel mio hub dedicato alla sicurezza AI per aziende trovo raccolti gli articoli tecnici offensive-oriented che uso quotidianamente nel lavoro di consulenza.

Come è strutturato il prompt del classificatore

Il system prompt del classificatore è lungo circa 1.200 parole e contiene quattro sezioni. La prima definisce il perimetro: l'agente deve analizzare codice PHP di autenticazione e rispondere con output strutturato JSON validato contro uno schema. La seconda elenca le cinque classi di vulnerabilità con esempi positivi e negativi specifici - few-shot prompting con 2 good example e 3 bad example per ogni classe. La terza sezione impone il formato di output rigido: per ogni finding restituisce classe, riga/file, snippet del codice incriminato, severity (critical/high/medium/low), e confidence (percentuale). La quarta sezione elenca esplicitamente le classi di false positive da evitare, costruita iterativamente dai feedback delle mie revisioni manuali.

L'input al classificatore non è mai il codebase intero: un pre-processor estrae i file rilevanti (tipicamente *AuthController.php, *JwtService.php, *TokenGuard.php, middleware/*.php), più lo schema Composer delle dipendenze (per verificare versioni note-vulnerable), più la configurazione framework (guards, providers) estratta da config/auth.php o equivalente. Il contesto totale si mantiene sotto i 40.000 token per richiesta, permettendo l'uso di Claude Sonnet 4.6 o di modelli equivalenti senza passare al context window da 1 milione di Opus.

L'output validation a valle è non negoziabile: schema JSON strict con campi obbligatori, controllo che la severity sia tra le quattro ammesse, verifica che il file path citato esista davvero nel codebase sotto analisi. OWASP Top 10 2025 classifica come LLM05 Improper Output Handling la classe di bug derivante dall'assumere che l'output LLM sia ben formato; uno schema validator a valle è la contromisura architetturale.

Come gestisco i falsi positivi e i falsi negativi

I falsi positivi sono gestiti in tre modi. Primo: un feedback loop esplicito - quando marco una segnalazione come non applicabile, la motivazione entra in un corpus di negative example che arricchisce il prompt few-shot nella successiva iterazione. Secondo: un human-in-the-loop gate sugli output marcati critical - nessun blocco automatico del merge, ma una segnalazione visibile nel code review che richiede sign-off da un revisore con competenza auth. Terzo: una soglia di confidenza configurabile - sotto il 70% di confidence il finding va nel report come "sospetta teorica" e non blocca il workflow.

I falsi negativi sono più pericolosi e meno visibili. La mitigazione primaria è non trattare mai il classificatore LLM come l'unico strato di audit - nella mia pipeline è il first-pass filter che cattura i pattern ricorrenti; il second-pass è un pentest manuale su sezioni critiche, fatto da un essere umano con competenza offensive. Gartner nel press release del 25 giugno 2025 stima che oltre il 40% dei progetti agentic AI verrà cancellato entro fine 2027 per inadequate risk controls, e la ragione più comune del fallimento è esattamente trattare l'agente come oracle invece che come filter. La defense in depth vale per l'auth tanto quanto per il code review.

Qual è il limite reale del metodo

Il classificatore LLM identifica pattern ricorrenti a livello di codice sorgente; non può verificare il comportamento runtime, non testa contro attacchi attivi, non rileva logic flaw specifici dell'applicazione - per esempio un endpoint che, dopo aver validato correttamente il JWT, ignora il claim role e usa invece un parametro passato dal client. Quel tipo di bug richiede test end-to-end con attacker mindset, e nessun LLM oggi disponibile lo copre con affidabilità. La mia pipeline lo sa e non pretende di coprirlo: produce un report che dice esplicitamente "classe A coperta, classe B coperta, classe C parziale, classe D fuori scope".

Il valore economico reale è nel tempo recuperato sul first-pass review. Sul campione della mia sandbox il ciclo completo di classificazione di una codebase Laravel/Symfony tipica dura 12-18 minuti contro le 4-6 ore di un review manuale equivalente. Le 4-6 ore del senior offensive auditor vanno comunque spese - ma su pattern non coperti dal classificatore, su logic flaw, su threat modeling specifico dell'applicazione. Il gain non è "sostituire l'esperto"; è "spostare l'esperto dal lavoro ripetitivo al lavoro cognitivamente difficile", che è esattamente la definizione operativa di produttività ingegneristica.

Va segnalato anche il costo nascosto di questo approccio: il prompt engineering del classificatore non è one-shot. Nella mia esperienza l'iterazione iniziale del prompt ha richiesto circa 40 ore di lavoro tra redazione, few-shot example curation, regression harness su un test set manualmente etichettato di 50 codebase. Ogni volta che il modello di base cambia versione - ho visto due cambi significativi tra ottobre 2025 e aprile 2026, da Claude Sonnet 4.5 a 4.6 - serve un re-tuning parziale perché il comportamento oscilla in modo non trascurabile sui corner case. Chi vuole adottare questo approccio in azienda deve budgetare esplicitamente queste ore come costo ricorrente, non come investimento one-time; la pipeline LLM per audit è un sistema vivo che richiede manutenzione quanto il codebase che analizza.

Se il tuo progetto PHP ha un sistema di autenticazione custom o basato su libreria standard ma non ha ancora avuto un audit strutturato, il modulo di preventivo gratuito ti risponde in sette domande - circa due minuti - e ti dice se il tuo scenario rientra nel mio ambito o ti indirizzo verso figure più adatte. Sui codebase dove il lavoro parte prima dall'hardening di base dell'infrastruttura che ospita l'auth, trovi un inquadramento operativo nel mio articolo su hardening Laravel e Symfony checklist NIS2-ready in 14 giorni e nella guida su incident response 72 ore per Laravel e Symfony. L'automazione LLM dell'audit è uno strumento potente; ma è potente solo quando l'expertise umana sa valutarne i finding e colmare i gap che l'automazione non raggiunge.

Ultima modifica: