Blog
Starlette BadHostCVE-2026-48710Sicurezza FastAPIAI Stack

BadHost (CVE-2026-48710): il bypass di autenticazione di FastAPI ora nei ransomware

'BadHost' (CVE-2026-48710) è un bypass di autenticazione da un carattere nell'header Host di Starlette, il framework sotto FastAPI, vLLM e LiteLLM — CVSS 6.5, ora in KEV e concatenato ai ransomware.

Zero Hunt Research··9 min di lettura

Il 27 maggio 2026 un audit di sicurezza finanziato dall'Open Source Technology Improvement Fund ha divulgato un bug di gravità media in Starlette, il framework ASGI Python che sta sotto FastAPI. Punteggio: CVSS 6.5. La maggior parte dei team ha letto il numero, lo ha messo dietro al backlog dei CVSS-9 ed è andata avanti. Il 2 settembre 2026 CISA ha aggiunto CVE-2026-48710 al catalogo Known Exploited Vulnerabilities, perché gli attaccanti avevano trasformato quel "6.5" nel primo anello di una catena che finisce in esecuzione di codice remoto e ransomware Qilin. Questa è la storia di come il numero di severità ha mentito — e di come mente nel modo più pericoloso proprio per il software che non sapevi di eseguire.

Cos'è davvero BadHost (CVE-2026-48710)

Starlette, come ogni framework ASGI, deve ricostruire l'URL completo di una richiesta in arrivo perché il codice applicativo possa ispezionarlo. Lo fa nel modo ovvio: concatena schema, header HTTP Host grezzo e path — grosso modo f"{scheme}://{host_header}{path}" — e poi ri-parsa il risultato in un oggetto URL. Il bug, ribattezzato BadHost dai ricercatori di X41 D-Sec che lo hanno trovato, è che fino alla versione 1.0.1 Starlette non validava mai il valore Host contro RFC 9112 §3.2 o RFC 3986 §3.2.2 prima di quella concatenazione.

Metti nell'header Host un carattere che non appartiene a un hostname — un /, un ?, un # — e il confine tra authority e path si sposta quando la stringa viene ri-parsata. Una richiesta che il server instrada davvero verso /foo può essere fatta apparire con un request.url.path di /abc:

GET /foo HTTP/1.1 Host: example.com/abc?bar=

Il routing avviene su /foo. Ma request.url.path ora legge /abc.

Questa divergenza è l'intera vulnerabilità. Il routing usa il path reale; qualunque middleware che basi una decisione di sicurezza su request.url.path vede quello controllato dall'attaccante. Se il tuo layer di autorizzazione dice "blocca tutto sotto /admin se non autenticato", un attaccante raggiunge /admin mentre request.url.path riporta qualcosa di innocuo — e il cancello si apre.

Il vettore CVSS — AV:N/AC:L/PR:N/UI:N/C:L/I:L/A:N — descrive tutto ciò in modo onesto e fuorviante allo stesso tempo. Onesto, perché isolata la primitiva perde poco e altera poco. Fuorviante, perché il vettore non può sapere cosa fa il tuo middleware con request.url. La severità di BadHost non è una proprietà di Starlette. È una proprietà della tua applicazione.

Perché un "6.5" gira sotto ogni server AI che hai

Starlette non è una libreria di nicchia. Ha oltre 400.000 progetti dipendenti su GitHub ed è il layer di trasporto sotto una quota notevole dell'attuale AI stack:

  • FastAPI — il modo di default per pubblicare un'API Python nel 2026, incluso quasi ogni endpoint interno di model-serving.
  • vLLM — uno dei server di inferenza open-source più diffusi.
  • LiteLLM — il proxy OpenAI-compatibile che sta davanti a decine di backend di modelli nei gateway aziendali.
  • Server MCP — gli endpoint Model Context Protocol a cui si connettono i tool agentici, quasi tutti costruiti su FastAPI.

I team più esposti sono esattamente quelli che si muovono più in fretta: infrastruttura AI che esegue FastAPI direttamente, senza un reverse proxy irrobustito davanti, perché è nata come banco di prova in un notebook ed è passata silenziosamente in produzione. È lì che il middleware basato sul path — "gli endpoint interni richiedono questo header, quelli pubblici no" — è più probabile che sia l'unica cosa tra una richiesta anonima e un modello, un vector store o un set di chiavi API dei provider.

BadHost non è diventata una voce KEV da sola. Lo stesso batch CISA del 2 settembre ha aggiunto CVE-2026-49869, una OS command injection non autenticata nel workflow engine Kestra (CVSS 10.0) che Microsoft ha osservato usata per lanciare reverse shell, enumerare il socket Docker e distribuire crypto miner, e CVE-2026-59822, un difetto di autenticazione impropria in LiteLLM stesso (CVSS 8.8). Letto per intero, il batch dice una cosa netta: lo stack AI-serving Python è sotto sfruttamento coordinato e opportunistico adesso, non in una previsione.

Da un carattere all'esecuzione di codice remoto

Ecco la parte che trasforma un 6.5 in un incidente. Secondo CISA e The Hacker News, attori legati al ransomware Qilin hanno concatenato BadHost con CVE-2026-42271, il difetto MCP di LiteLLM di cui abbiamo scritto a giugno, per ottenere esecuzione di codice remoto sui deployment vulnerabili.

La catena si legge come una lezione da manuale di exposure management:

Passo Primitiva CVSS isolata Effetto
1 BadHost (CVE-2026-48710) 6.5 Bypass del middleware di auth basato sul path via header Host
2 Raggiungere una route interna protetta La superficie di management/MCP "raggiungibile solo internamente"
3 RCE MCP di LiteLLM (CVE-2026-42271) critico Esecuzione di codice sull'host del gateway
4 Post-exploitation Qilin Staging, movimento laterale, cifratura

Nessun singolo anello di quella catena è un CVSS 10 che avrebbe scavalcato la tua coda. Il primo è un 6.5. Il secondo è "è interno, quindi l'auth davanti è best-effort". Composti, sono un percorso pre-autenticazione all'esecuzione di codice sulla macchina che custodisce le chiavi dei tuoi provider di modelli. È esattamente il fallimento che il vulnerability management scanner-e-punteggio è costruito per mancare: valuta le vulnerabilità una alla volta, e il rischio vive nella composizione.

Perché CVSS 6.5 è il numero sbagliato su cui pianificare

CVSS risponde a "quanto è grave questa vulnerabilità in astratto". Non può rispondere a "è sfruttabile nel mio ambiente, e cosa raggiunge". Per BadHost il divario tra le due domande è enorme:

  • In un'app che non usa mai request.url.path per l'autorizzazione, BadHost è quasi innocua.
  • In un'app il cui intero modello di accesso è middleware a prefisso di path davanti a un proxy LiteLLM, BadHost è la porta d'ingresso, e la sua severità reale è ciò che sta dietro quella porta — qui, RCE.

L'advisory non può sapere quale delle due sei tu. Solo un reale tentativo di sfruttamento contro il tuo specifico deployment può. Ed è l'argomento per validare l'esposizione sfruttandola in una copia sicura, invece di ereditare il punteggio senza contesto di un vendor — pattern su cui torniamo alla fine.

Remediation

Trattala come un difetto attivamente sfruttato, in KEV, con scadenza federale di remediation al 16 settembre 2026. Lavora il runbook dall'alto in basso; la sola patch non ti dice se eri già stato concatenato.

1. Sono interessato?

Inventaria ogni servizio che spedisce Starlette — direttamente o transitivamente via FastAPI, vLLM, LiteLLM o un server MCP. Qualunque cosa a Starlette ≤ 1.0.0 è vulnerabile.

# Per-ambiente: che versione di Starlette è davvero installata?
python -c "import starlette; print(starlette.__version__)"

# Trovarla su una flotta di venv / container
pip list 2>/dev/null | grep -i starlette
grep -rEi '^starlette' */requirements*.txt poetry.lock uv.lock 2>/dev/null

# Quali di quei servizi basano davvero l'auth su request.url.path? (i realmente sfruttabili)
grep -rEn 'request\.url\.path|request\.url[^.]' --include=*.py .

L'ultimo grep conta più del controllo di versione: separa "dipendenza vulnerabile presente" da "sfruttabile in pratica".

2. Patch — versione corretta esatta

Aggiorna Starlette a 1.0.1 o successiva. La 1.0.1 valida l'header Host contro RFC 9112 §3.2 / RFC 3986 §3.2.2 e ripiega su scope["server"] quando l'header è malformato. Ricostruisci e ridistribuisci ogni immagine a valle — i container FastAPI, vLLM, LiteLLM e MCP fissano Starlette transitivamente, quindi bumpare il pacchetto di primo livello non basta; conferma la versione risolta dentro il container in esecuzione.

Patcha LiteLLM nello stesso passaggio: la metà RCE della catena osservata è CVE-2026-42271, e CVE-2026-59822 (auth impropria) è nello stesso batch KEV.

3. Non puoi patchare subito? Controlli compensativi

  • Valida l'header Host al bordo. Metti un reverse proxy (nginx, Envoy, HAProxy) davanti e rifiuta ogni richiesta il cui Host contenga /, ?, #, @ o spazi prima che raggiunga l'app. Una allow-list rigida di hostname attesi è meglio di una deny-list di caratteri cattivi.
  • Smetti di fidarti di request.url.path per l'autorizzazione. Basa il controllo su scope["path"] ASGI (il path instradato) o sulle dependency di route del framework, non sull'URL ricostruito.
  • Imposta una allow-list di host fidati in Starlette/FastAPI (TrustedHostMiddleware) — necessaria ma non sufficiente da sola, quindi accoppiala al controllo al bordo.
  • Non esporre direttamente endpoint di model-serving o MCP. Se FastAPI risponde su un'interfaccia pubblica senza proxy, quella è l'esposizione da chiudere per prima.

4. Caccia alla compromissione

Poiché routing e URL ricostruito divergono, l'indizio è nella request line grezza, non nei log applicativi che registrano request.url.path. Mappa la caccia su MITRE ATT&CK:

  • T1190 (Exploit Public-Facing Application): cerca nei log di proxy/access header Host che contengano /, ?, # o @. I client legittimi non li inviano mai.
  • T1071.001 (Web Protocols) / T1059 (Command and Scripting Interpreter): sugli host LiteLLM/MCP, cerca esecuzioni curl- o wget-pipe-to-shell, connessioni in uscita inattese dal processo del gateway e processi figli generati dal worker Python.
  • T1610 (Deploy Container) / T1496 (Resource Hijacking): l'attività Kestra nella stessa campagna si è appoggiata all'accesso al socket Docker e ai crypto miner — verifica su ogni host AI container inattesi e CPU sostenuta.
  • T1486 (Data Encrypted for Impact): in caso di sospetto coinvolgimento Qilin, osserva il classico pattern staging-poi-cifra — un host che storicamente serviva solo inferenza che di colpo genera alto volume in uscita e poi scritture rapide su share.

5. Eradicazione + verifica

Se trovi prove della catena: isola l'host, rimuovi ogni binario o container rilasciato e ruota ogni credenziale che viveva sul gateway — chiavi API dei provider di modelli, stringhe di database e ogni token che il proxy LiteLLM deteneva sono da considerare compromessi. Poi conferma la fix dopo aver patchato: rigioca una richiesta stile Host: example.com/admin?x= contro una route protetta in una copia di staging e verifica che request.url.path ora rifletta il path instradato e che il gate di auth tenga.

Dove si inserisce Zero Hunt

Il tratto distintivo di BadHost è che il controllo pensato per fermarla — il middleware di autorizzazione basato sul path — è esattamente ciò che viene aggirato. La tua regola WAF ancorata a /admin, il tuo middleware ancorato a request.url.path: entrambi guardano il valore che l'attaccante ha riscritto. Quando il layer di controllo accessi è cieco per costruzione, la superficie che vede ancora l'attacco è la rete.

È qui che il pilastro AI Traffic Analysis di Zero Hunt guadagna il suo posto. L'appliance esegue un modello di deep learning proprietario con quattro teste di inferenza parallele — traffico sospetto, classificazione malware, identificazione del tipo di attacco e fingerprinting applicativo — sul traffico live a un baseline di 2.7+ Gbit/s, sulla GPU dell'appliance, senza callback verso il cloud. Un header Host malformato è un'anomalia sul filo che il middleware lo validi o no; il callback dell'RCE LiteLLM, il fetch curl-pipe-shell, il check-in del miner e lo staging in uscita di Qilin sono tutti visibili comportamentalmente mentre accadono, non nel digest SIEM del mattino dopo. La catena che i tuoi controlli basati sul path erano strutturalmente incapaci di intercettare è una firma di traffico per un modello addestrato su miliardi di sequenze PCAP.

La seconda lezione riguarda il "6.5". Un punteggio senza contesto ha detto alla maggior parte dei team che si poteva rimandare; l'unico modo per sapere se il tuo specifico deployment FastAPI o LiteLLM trasforma quel 6.5 in un RCE è tentare la catena contro di esso. Il pentest generativo a 10 agenti di Zero Hunt fa esattamente questo: campagne change-triggered ri-testano un servizio nell'ora in cui cambia, l'LLM locale scrive un exploit per-target che riflette il tuo middleware invece di un PoC generico, ogni tentativo è backtestato nell'AI Gym prima di essere eseguito e ogni finding è firmato ECDSA per l'audit trail. Risponde all'unica domanda che il vettore CVSS non può — è sfruttabile qui — con evidenza invece che con una stima.

BadHost ricorda che il numero di severità è un'offerta d'apertura, non un verdetto. Il verdetto è ciò che la catena raggiunge nel tuo ambiente. Guarda come la piattaforma lo valida, o contattaci.