flawopen.com/AI Security/mlflow-webhook-ssrf-cve-2026-64849
CVE-2026-64849: MLflow Webhook SSRF
CVE-2026-64849 is an unauthenticated server-side request forgery in MLflow webhook delivery (High, CVSS 3.1: 8.6). The initial URL check did not constrain redirect targets or the address used by the actual connection; the webhook test endpoint could return the upstream response body.[1]
A receptionist checks the address written on a parcel, then lets the courier follow a forwarding note without checking the new destination. MLflow checked the webhook URL before connecting, but redirects and a second DNS lookup could send the request somewhere else. The fix checks the address of the actual connected peer before sending HTTP data.[1], [2]
Core Concepts & Subsystem Terms
SSRF- A server makes a network request chosen by a caller, crossing the server’s network trust boundary.[1]
Webhook test endpoint- MLflow API that sends a test event to a configured webhook; on affected default servers its response body was returned to the caller.[1]
Framework mappings
- CWE reference for the weakness described in this guide: CWE-918
- D3-OTF Outbound Traffic Filtering — This guide's outbound network restriction limits which destinations the service or host can reach.
- CISA KEV — added 2026-08-19 — CVE-2026-64849
Step-by-Step Attack Flow
A reachable MLflow server accepts a webhook test
The advisory describes an unauthenticated default Tracking Server configuration. Authentication plugins and deployment controls can change reachability; confirm the actual configuration. CISA added the CVE to its Known Exploited Vulnerabilities catalog on August 19, 2026; inclusion signals known exploitation, but does not establish compromise of any particular deployment.[1], [4], [5]
The first URL passes validation
_validate_webhook_url resolves and checks the original hostname, but the checked address is discarded rather than bound to the later socket.[1]
A redirect or second lookup reaches another destination
The HTTP client follows redirects, and the connection can resolve the hostname again. The webhook test response reflects the upstream body, making this a full-read SSRF.[1]
The upstream fix checks the connected peer
PR #24258 validates the socket peer immediately after connect and before TLS/HTTP data; its session also avoids environment proxy routing. MLflow 3.15.0 fixes the CVE; separate IPv6-transition bypasses in the guard were fixed in 3.16.0. The advisory credits freeman-bb as the original private reporter and AUTHENSOR as an independent finder.[1], [2], [3], [6]
Source Code: Flaw vs. Secure Implementation
These excerpts show the original URL-validation/request boundary and the upstream connection-time peer check. They omit exploit requests and internal targets. The 3.16.0 IPv6 normalization is a later, separate hardening change; do not merge those follow-up advisories into CVE-2026-64849.[2]
# Excerpt from mlflow/webhooks/delivery.py before 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
# Excerpt from the upstream connection-time fix
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)
Engineering & System Hardening Checklist
- ✓Upgrade MLflow 3.10.0–3.14.x to at least 3.15.0 for
CVE-2026-64849; prefer the current supported release. Deploy 3.16.0 or later where IPv6 transition mechanisms may be used, to include the later SSRF-guard fixes.[1], [3], [6] - ✓Verify the Tracking Server is not exposed to untrusted networks; determine whether the optional authentication plugin and authorization checks are enabled. Network restriction is defense in depth, not a substitute for upgrading.[1]
- ✓Restrict server egress to required destinations and deny loopback, link-local, private, and cloud-metadata ranges at the network layer. Revalidate each redirect and connected peer in outbound-fetch code. Keep
MLFLOW_WEBHOOK_ALLOW_PRIVATE_IPSdisabled in production; allow it only in isolated development environments with an explicit need.[2], [7] - ✓Add CI regression tests proving that direct non-public addresses, redirect destinations, DNS rebinding, HTTP/HTTPS pools, and proxy routes are rejected before request data is sent; test the private-IP override separately.[2], [7]