FlawOpen

flawopen.com/AI Security/mlflow-webhook-ssrf-cve-2026-64849

● CVE-2026-64849 · CVSS 8.6 · Hoch
Sicherheitsforschung · FlawOpen

CVE-2026-64849: MLflow-Webhook-SSRF

CVE-2026-64849 ist eine unauthentifizierte serverseitige Anforderungsfälschung (SSRF) bei der MLflow-Webhook-Zustellung (hoch, CVSS 3.1: 8.6). Die Prüfung der ursprünglichen URL begrenzte weder Weiterleitungsziele noch die Adresse der tatsächlichen Verbindung; der Test-Endpunkt konnte den Antworttext des Ziels zurückgeben.[1]

💡 Einfache Erklärung (ELI5)

Stellen Sie sich einen Empfang vor, der die Paketadresse prüft, den Zusteller aber einer Weiterleitungsnotiz folgen lässt, ohne das neue Ziel zu kontrollieren. MLflow prüfte die Webhook-URL vor dem Verbindungsaufbau; Weiterleitungen und eine zweite DNS-Abfrage konnten die Anfrage dennoch woandershin leiten. Die Korrektur prüft die Adresse des tatsächlich verbundenen Peers, bevor HTTP-Daten gesendet werden.[1], [2]

Kernkonzepte & Begriffe

SSRF
Der Server führt eine vom Aufrufer gewählte Netzwerkanfrage aus und überschreitet damit seine Netzwerk-Vertrauensgrenze.[1]
Webhook-Test-Endpunkt
MLflow-API, die ein Testereignis an einen konfigurierten Webhook sendet; betroffene Standardserver gaben den Antworttext des Ziels zurück.[1]
DNS-Rebinding / TOCTOU
Ein Hostname löst bei der Prüfung zu einer öffentlichen und beim Verbindungsaufbau zu einer anderen, nicht öffentlichen Adresse auf.[1], [2]

Framework-Zuordnungen

Schritt-für-Schritt Angriffsablauf

Schritt 1

Ein erreichbarer MLflow-Server nimmt einen Webhook-Test an

Der Hinweis beschreibt einen nicht authentifizierten Tracking Server in der Standardkonfiguration. Authentifizierungs-Plugins und Bereitstellungskontrollen ändern die Erreichbarkeit; prüfen Sie die tatsächliche Konfiguration. CISA nahm die CVE am 19. August 2026 in den Katalog Known Exploited Vulnerabilities auf. Das weist auf bekannte Ausnutzung hin, belegt aber keine Kompromittierung einer bestimmten Installation.[1], [4], [5]

Schritt 2

Die erste URL besteht die Prüfung

_validate_webhook_url löst den ursprünglichen Hostnamen auf und prüft ihn, verwirft die geprüfte Adresse jedoch, statt sie an den späteren Socket zu binden.[1]

Schritt 3

Weiterleitung oder erneute Auflösung erreicht ein anderes Ziel

Der HTTP-Client folgt Weiterleitungen; die Verbindung kann den Hostnamen erneut auflösen. Der Test-Endpunkt spiegelte den Antworttext zurück und ermöglichte damit SSRF mit vollständiger Antwortlesbarkeit.[1]

Schritt 4

Der Upstream-Fix prüft den verbundenen Peer

PR #24258 prüft die Socket-Peer-Adresse direkt nach dem Verbindungsaufbau und vor TLS/HTTP-Daten; die Sitzung umgeht außerdem Umgebungs-Proxys. MLflow 3.15.0 behebt die CVE; separate IPv6-Übergangs-Umgehungen sind in 3.16.0 behoben. Die Meldung nennt freeman-bb als ursprünglichen vertraulichen Melder und AUTHENSOR als unabhängigen Finder.[1], [2], [3], [6]

Quellcode: Verwundbar vs. Sicher

Die Auszüge zeigen die ursprüngliche Grenze zwischen URL-Prüfung und Anfrage sowie die Upstream-Prüfung des Peers beim Verbindungsaufbau. Exploit-Anfragen und interne Ziele fehlen. Die IPv6-Normalisierung in 3.16.0 ist eine spätere, separate Härtung.[2]

VERWUNDBARE IMPLEMENTIERUNG
# Auszug aus mlflow/webhooks/delivery.py vor PR #24258.
_validate_webhook_url(webhook.url)  # checks the initial URL only
return session.post(
    webhook.url, data=payload_bytes, headers=headers, timeout=timeout
)  # redirects follow by default; the validated IP is not pinned
GEHÄRTETER SICHERHEITS-PATCH
# Auszug aus dem Upstream-Fix mit Prüfung des Peers beim Verbindungsaufbau.
def _assert_public_peer(sock):
    if _MLFLOW_WEBHOOK_ALLOW_PRIVATE_IPS.get():
        return
    try:
        peer_ip = sock.getpeername()[0]
    except OSError as exc:
        sock.close()
        raise SSRFProtectionError(
            f"Could not determine webhook connection peer address: {exc}"
        ) from exc
    try:
        ip = ipaddress.ip_address(peer_ip)
    except ValueError as exc:
        sock.close()
        raise SSRFProtectionError(
            f"Webhook connection resolved to an invalid IP address: {peer_ip!r}"
        ) from exc
    if not ip.is_global:
        sock.close()
        raise SSRFProtectionError(
            f"Webhook connection blocked: {ip} is not a public IP address. "
            "This may indicate a DNS rebinding attempt."
        )


class _SSRFProtectedHTTPConnection(HTTPConnection):
    def _new_conn(self):
        sock = super()._new_conn()
        _assert_public_peer(sock)
        return sock


class _SSRFProtectedHTTPSConnection(HTTPSConnection):
    def _new_conn(self):
        sock = super()._new_conn()
        _assert_public_peer(sock)
        return sock

adapter = SSRFProtectedHTTPAdapter(max_retries=retry_strategy)
session = requests.Session()
session.trust_env = False
session.mount("http://", adapter)
session.mount("https://", adapter)

Checkliste für Engineering & Systemsicherheit

Quellen

← AI Security Directory →