MLflow CVE-2026-64849: un SSRF non autenticato che ruba le chiavi cloud
L'SSRF non autenticato di MLflow (CVE-2026-64849, CVSS 9.3) è stato sfruttato in poche ore per raggiungere i metadata endpoint e sottrarre credenziali IAM. Cosa è rotto e il runbook di fix.
Il tracking server che i tuoi data scientist hanno lasciato su un IP pubblico è ora un proxy dentro il tuo account cloud. CVE-2026-64849 è una server-side request forgery non autenticata e in lettura completa in MLflow — la piattaforma di experiment-tracking e model-registry più diffusa nello stack di machine learning — e gli attaccanti hanno iniziato a scansionarla poche ore dopo l'assegnazione dell'identificativo, il 17 agosto 2026. Ha un CVSS di 9.3, non richiede credenziali, e ciò che rilegge è qualunque cosa viva sulla tua rete interna: soprattutto il metadata endpoint dell'istanza cloud, che consegna chiavi IAM a breve durata a chiunque riesca a far porre la domanda all'host.
Non è una catena teorica. La società di sicurezza watchTowr ha dichiarato che la sua rete di honeypot Attacker Eye ha intercettato attori malevoli intenti a "scansionare indiscriminatamente istanze MLflow esposte in rete" lo stesso giorno della pubblicazione del CVE, con l'obiettivo esplicito di estrarre "credenziali e segreti da indirizzi IP interni e servizi ben noti", come riporta The Hacker News. VulnCheck ha confermato l'attività in modo indipendente. CISA è stata abbastanza rapida da aggiungere il bug al suo catalogo Known Exploited Vulnerabilities il 19 agosto, con scadenza di remediation federale al 2 settembre.
Cos'è davvero CVE-2026-64849
Il model registry di MLflow può inviare webhook quando accadono eventi — la promozione di una versione di modello, il cambio di un tag. Per permettere agli operatori di verificare un webhook, il server espone un endpoint di test: POST /api/2.0/mlflow/webhooks/{id}/test. In un'installazione di default quell'endpoint è non autenticato, perché MLflow non ha alcuna autenticazione integrata e si aspetta che sia tu ad aggiungerla.
Il server prova a essere prudente. Prima di consegnare la richiesta, _validate_webhook_url() in mlflow/utils/validation.py verifica che l'URL di destinazione non risolva a un indirizzo privato, loopback o link-local — la classica difesa SSRF. Il problema, descritto nel GitHub Security Advisory GHSA-7gwp-5pfp-969j, è che il controllo e la connessione sono due atti separati e nulla passa dall'uno all'altro:
- Il validatore non fissa nulla. Risolve l'hostname, decide che l'IP risolto sembra pubblico, e poi lo butta via. Il codice di consegna in
mlflow/webhooks/delivery.pyri-risolve il nome e apre una connessione nuova. - I redirect non vengono mai ri-validati. Punti il webhook a un URL HTTPS controllato dall'attaccante che supera il controllo, poi rispondi con un
302versohttp://169.254.169.254/…. Il client HTTP lo segue e — trattandosi dell'endpoint test — riflette il corpo della risposta direttamente al chiamante. Lettura completa. - Il DNS rebinding chiude il varco che il redirect non copre. Poiché
getaddrinfo()viene eseguito due volte, un attaccante che controlla un record DNS può rispondere con un IP pubblico al momento della validazione e un IP link-local pochi microsecondi dopo, alla connessione. Un classico time-of-check/time-of-use.
Due bypass indipendenti, una radice sola: la difesa ha validato una stringa, non un socket.
Perché un SSRF in MLflow arriva alle tue chiavi cloud
Un SSRF è pericoloso solo quanto lo è ciò che l'host vulnerabile raggiunge e tu no. Su un laptop è una curiosità. Su un'istanza cloud è un distributore di credenziali, perché ogni grande cloud espone un metadata service link-local che il workload usa per recuperare la propria identità:
| Cloud | Metadata endpoint | Cosa legge un SSRF |
|---|---|---|
| AWS (IMDSv1) | http://169.254.169.254/latest/meta-data/iam/security-credentials/ |
Chiavi IAM temporanee del ruolo, nessun header richiesto |
| GCP | http://169.254.169.254/computeMetadata/v1/ |
Token del service account (serve l'header Metadata-Flavor) |
| Azure | http://169.254.169.254/metadata/identity/oauth2/token |
Token della managed identity (serve l'header Metadata:true) |
La riga AWS IMDSv1 è il colpo grosso: una singola GET non autenticata, nessun header custom, e la risposta è un set valido di chiavi temporanee per il ruolo IAM con cui gira l'istanza MLflow. In un ambiente ML quel ruolo è raramente minimale — di solito legge e scrive i bucket S3 con i dati di training, gli artefatti dei modelli e talvolta l'intero feature store. L'attaccante non deve violare il tuo cloud. È il tuo server MLflow ad autenticarsi per lui e a leggere la risposta ad alta voce.
"Abbiamo validato l'URL che ci hai dato." "Certo. Poi avete seguito il mio redirect verso il metadata service e mi avete riletto le chiavi IAM nella risposta di test. Avete validato una promessa, non una destinazione."
Quello scambio è l'intero bug. Tutto ciò che segue — enumerare S3, assumere ruoli, muoversi lateralmente nell'account — è ordinario mestiere cloud contro credenziali valide, ed è esattamente il motivo per cui elude i controlli addestrati a riconoscere quelle non valide.
La patch che un redirect ha scavalcato
È la parte su cui vale la pena fermarsi, perché è uno schema, non un incidente. MLflow aveva già irrobustito la validazione degli URL dei webhook in una release precedente. Yordan Ganchev di watchTowr ha osservato che il nuovo bug "aggira le patch precedenti per il modo in cui gestisce i redirect web". Il fix precedente ragionava sull'URL digitato dall'operatore. Non ha mai ragionato sull'URL con cui il client HTTP avrebbe finito per parlare dopo un 302 o un rebind.
Ogni allowlist SSRF che valida un hostname invece del socket connesso ha questo buco. Il fix corretto — rilasciato in MLflow 3.15.0 — valida a livello di rete: la release introduce un SSRFProtectedHTTPAdapter che verifica l'IP del peer di ogni socket subito dopo connect(), così le destinazioni dei redirect e le risoluzioni rebound vengono ricontrollate nell'unico momento non falsificabile. Quel singolo cambio di design chiude insieme la variante 302-read, la variante 307/308-write e il TOCTOU del DNS rebinding. Validare stringhe è indovinare; validare il peer connesso è sapere.
Il tuo stack AI è ora il bersaglio debole
MLflow è un solo punto in uno spostamento molto più ampio. Il "Frontier AI Vulnerability Burst" di Unit 42, pubblicato il 4 agosto 2026, descrive un sistema autonomo che ha fatto emergere 14.090 vulnerabilità confermate su 3.915 progetti open source, circa il 40% di gravità alta o critica. Gran parte dell'ecosistema di tooling ML/AI — tracker, registry, framework di serving, orchestratori di agenti — è stata scritta per una rete di laboratorio fidata e ora siede sul perimetro con endpoint aperti di default. La superficie d'attacco che apparteneva alle web app e agli appliance VPN comprende oggi la tubatura della tua pipeline di modelli.
Le proprietà scomode di quella tubatura:
- Gira con ruoli cloud generosi, perché il training legge e scrive molto storage.
- Arriva con auth disattivata di default, e MLflow ne è il caso da manuale.
- Vive vicino ai gioielli — i dati e i modelli — pur essendo sorvegliata come uno strumento di sviluppo, non come un servizio di produzione esposto.
- Ora è scoperta dalle macchine, non dalle persone, quindi la finestra tra "rilasciato" e "scansionato" è di ore, come CVE-2026-64849 ha dimostrato in tempo reale.
Remediation
Tratta ogni istanza MLflow raggiungibile da Internet e sotto la 3.15.0 come credenziali esposte fino a prova contraria, non semplicemente vulnerabile. La lettura dei metadata è rapida e lascia poco sulla macchina MLflow stessa.
1. Sono interessato?
Controlla versione ed esposizione:
# Versione — qualunque cosa < 3.15.0 è vulnerabile
python -c "import mlflow; print(mlflow.__version__)"
mlflow --version
# Il tracking server è raggiungibile e l'endpoint di test è aperto?
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST http://<mlflow-host>:5000/api/2.0/mlflow/webhooks/0/test
# Un 400/404 (non 401/403) significa che l'endpoint risponde senza autenticazione — sei esposto.
Se l'host gira su AWS/GCP/Azure con un ruolo o una managed identity collegata, ed era raggiungibile da Internet prima della patch, passa al punto 4 a prescindere da cosa mostrino i log.
2. Patch — versione corretta esatta
Aggiorna a MLflow 3.15.0 o successiva (GHSA-7gwp-5pfp-969j). È l'unico fix completo; aggiunge la validazione dell'IP del peer al momento della connessione, che sopravvive a redirect e rebinding.
pip install --upgrade "mlflow>=3.15.0"
3. Non puoi patchare subito? Controlli compensativi
Impilali; nessuno da solo è sufficiente.
- Imponi IMDSv2 con hop limit 1 (AWS):
aws ec2 modify-instance-metadata-options --http-tokens required --http-put-response-hop-limit 1 --http-endpoint enabled. Il requisito del token sconfigge la sempliceGET; l'hop-limit-1 blocca le fughe dai container. Su GCP/Azure l'equivalente è l'header di metadata obbligatorio, che un SSRF semplice non può aggiungere. - Filtra l'egress dell'host MLflow così che non possa originare connessioni verso
169.254.169.254,fd00:ec2::254, loopback o range RFC1918 che non ha motivo di raggiungere. - Metti un'autenticazione davanti a MLflow — reverse proxy con authn, o
--app-name basic-authdi MLflow. Di default non ne ha. - Toglilo da Internet. Un tracking server non ha nulla da fare su un IP pubblico; legalo alla VPC e raggiungilo via bastion o private link.
4. Cerca segni di compromissione
Segnali, mappati su MITRE ATT&CK:
- Richieste in uscita dall'host MLflow verso
169.254.169.254/fd00:ec2::254— la lettura dei metadata (T1552.005, Unsecured Credentials: Cloud Instance Metadata API). POST /api/2.0/mlflow/webhooks/{id}/testda sorgenti esterne o non autenticate, soprattutto dove l'URL del webhook risolve a un indirizzo privato o link-local (T1190, Exploit Public-Facing Application).- Uso del ruolo dell'istanza da un IP che non è l'istanza — in CloudTrail, le chiavi temporanee del ruolo chiamate da un ASN esterno (T1078.004, Valid Accounts: Cloud Accounts).
- Egress anomalo da un host che storicamente solo ingerisce — un nodo di training che di colpo fa richieste web in uscita prolungate.
- Accesso al model registry / experiment store che non hai avviato tu (T1213, Data from Information Repositories).
5. Eradica e verifica
- Ruota ogni credenziale che l'host poteva raggiungere — la sessione del ruolo dell'istanza, eventuali chiavi a lunga durata in ambiente o config, credenziali di registry e database, e i segreti salvati nei webhook di MLflow. Se IMDSv1 era attivo, dai per scontato che le chiavi del ruolo siano trapelate.
- Rivedi i log di CloudTrail / audit cloud per usi del ruolo esposto dall'esterno della tua infrastruttura, e per chiamate
sts:AssumeRole,s3:GetObjectosecretsmanager:GetSecretValueche non sai giustificare. - Conferma la bonifica dopo la patch, non prima: ruotare le chiavi mentre l'SSRF è ancora aperto significa consegnare all'attaccante quelle nuove. Patcha, poi ruota, poi verifica che con la vecchia identità non resti accesso residuo.
Dove si inserisce Zero Hunt
La metà pericolosa di questo bug non è l'SSRF — sono i secondi dopo l'SSRF, quando una sessione IAM valida esce dal tuo account dalla porta principale. I controlli basati su firme e identità la lasciano passare, perché dal punto di vista del cloud la tua istanza MLflow non ha fatto nulla che non le sia permesso. A tradirla è il traffico: un host di tracking che di norma preleva solo artefatti apre all'improvviso una sessione verso l'indirizzo link-local dei metadata, seguita da un picco di egress verso una destinazione mai vista prima, man mano che le chiavi rubate vengono usate.
È esattamente la firma che il motore di AI Traffic Analysis di Zero Hunt è stato costruito per intercettare. Un modello di deep learning proprietario con quattro teste di inferenza parallele — traffico sospetto, classificazione malware, identificazione del tipo di attacco, fingerprinting applicativo — addestrato su miliardi di sequenze PCAP e in esecuzione localmente sulla GPU dell'appliance a 2,7+ Gbit/s, segnala il raggiungimento del metadata endpoint e la sessione in uscita anomala mentre accadono, non nel digest del SIEM del mattino dopo. Vede il pivot SSRF-verso-credenziali come anomalia comportamentale, che il CVE abbia già una firma oppure no.
A monte, il pentest generativo a 10 agenti di Zero Hunt tratta una nuova istanza MLflow sul perimetro come una campagna change-triggered: il motore scrive un exploit per-target che concatena il bypass via redirect e via DNS rebinding, dimostra se il tuo IMDS è raggiungibile e — poiché il finding è firmato ECDSA con la sua catena di evidenze completa — ti consegna un record difendibile di esattamente quale ruolo cloud è stato esposto, così la rotazione al punto 5 colpisce l'identità giusta invece di ogni chiave che possiedi. Valida l'esposizione prima che lo faccia un attaccante; osserva il filo per il momento in cui ci provano comunque.