Your ML experiment tracker just became a free proxy into your cloud metadata service, no password required
CVE-2026-64849 is an unauthenticated Server-Side Request Forgery (SSRF) in MLflow's model-registry webhook delivery system. The POST /api/2.0/mlflow/webhooks/{id}/test endpoint calls _validate_webhook_url() on the caller-supplied URL, but when the HTTP client follows a 302 redirect, it re-resolves the redirect target without re-checking the SSRF allow-list. An attacker sends a webhook URL pointing to their own HTTPS server, which passes validation and then returns a 302 to http://169.254.169.254/latest/meta-data/iam/security-credentials/…. MLflow dutifully follows the redirect and echoes the IMDS response body — containing live AWS (or GCP/Azure) temporary credentials — straight back to the attacker. A second variant using 307/308 redirects preserves the POST method and body, letting attackers blind-write into internal management APIs (Docker daemon /stop, Elasticsearch /_close, Spring Boot Actuator /shutdown). Every MLflow release below 3.15.0 is affected, including the entire 2.x line which is de facto end-of-life and will never receive a backport.
The vendor-assigned CVSS 9.3 CRITICAL is accurate and, if anything, conservative. The endpoint requires zero authentication, has zero user interaction, and the scope change (S:C) reflects the fact that the SSRF breaks out of MLflow's trust boundary into cloud infrastructure. With 30,000+ internet-exposed instances (per Shodan/Censys surveys from mid-2026), active exploitation logged within hours of disclosure, CISA KEV listing within 48 hours, and multiple public PoCs on GitHub, this is the rare vuln where the vendor severity number matches the operational urgency exactly. There is no meaningful friction anywhere in the chain.
5 steps from start to impact.
Discover exposed MLflow Tracking Server
- MLflow Tracking Server reachable from the internet on default port 5000
- If the operator has enabled an auth plugin (e.g.,
mlflow-auth-basic) or placed the server behind a VPN/zero-trust gateway, this step fails
title:"MLflow". Censys query: services.http.response.html_title:"MLflow". External attack surface management tools (Wiz, Orca, Prisma Cloud) flag exposed MLflow instances.Create a webhook pointing to attacker-controlled HTTPS host
POST /api/2.0/mlflow/registered-models/create-webhook with a http_url_spec.url set to their own public HTTPS endpoint (e.g., https://evil.attacker.com/redir). The _validate_webhook_url() function resolves the hostname, confirms it is a public IP, and stores the webhook. This step passes validation cleanly because the attacker's host is public.- Network access to the MLflow API
- No authentication gate in front of the webhook API
- Organizations that restrict outbound HTTPS from the MLflow host to an allow-list of domains block the attacker's redirect server
Trigger the webhook test endpoint
POST /api/2.0/mlflow/webhooks/{id}/test. This unauthenticated endpoint synchronously fetches the stored webhook URL using requests.Session with redirect-following enabled. The attacker's server returns a 302 Found with Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>. MLflow follows the redirect to the IMDS endpoint without re-validating the target IP against the SSRF allow-list.- Webhook created in step 2
- MLflow host can reach link-local IMDS (169.254.169.254)
- If IMDSv2 (hop-limit=1) is enforced on the EC2 instance, the PUT-based token exchange fails from within the application layer — but IMDSv1 is still the default for many deployments
- GCP and Azure metadata endpoints have their own header requirements, though the 307/308 variant can supply custom headers
169.254.169.254 in redirect chains. Cloud provider IMDS access logs (AWS CloudTrail ec2:DescribeInstances won't catch this, but VPC Flow Logs to 169.254.169.254 can).Exfiltrate cloud credentials from IMDS response
/test endpoint returns the upstream response_status and response_body directly to the caller. The response body contains temporary IAM credentials (AccessKeyId, SecretAccessKey, Token) valid for up to 6 hours. The attacker now possesses valid cloud credentials scoped to whatever IAM role is attached to the MLflow host — typically a broad role with access to S3/GCS buckets containing training data, model artifacts, and secrets.- IMDS is accessible from the MLflow process (not blocked by host firewall or IMDSv2 hop-limit)
- If the IAM role is tightly scoped (least-privilege), blast radius is limited — but ML workload roles are notoriously over-permissioned
UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS fires when IMDS credentials are used from an IP outside the VPC.Cloud account takeover and lateral movement
aws sts get-caller-identity, aws s3 ls, aws secretsmanager list-secrets). They pivot to production databases, exfiltrate regulated data, poison ML model artifacts in S3, or create new IAM users for persistence. The blast radius is cloud-tenant-scale. In the 307/308 blind-write variant, the attacker can additionally POST shutdown commands to internal services (Docker, Elasticsearch, Spring Boot Actuator) causing denial of service.- Valid cloud credentials from step 4
- Cloud resources accessible to the stolen role
- Cloud-native detection (GuardDuty, Azure Defender, GCP SCC) may flag anomalous API calls from unusual IPs
- SCPs or VPC endpoint policies may limit credential use outside the account
HttpTokens=required and HttpPutResponseHopLimit=1 on the instance metadata service. This forces token-based IMDS access that the SSRF redirect cannot satisfy, blocking the primary credential-theft path on AWS. Does NOT protect against SSRF to other internal services. Deploy within 3 days.mlflow-auth-basic or integrate with your IdP (OIDC/SAML). This gates the webhook API behind credentials, preventing unauthenticated SSRF. Note: this requires MLflow 2.5+ and does not protect against authenticated insiders. Deploy within 3 days.169.254.169.254, metadata.google.internal, and RFC 1918 ranges. This breaks the redirect chain even if the SSRF fires. Deploy within 3 days.SSRFProtectedHTTPAdapter validating peer IP at connect() time. Version 3.16.0 closes IPv6-transition bypasses. For MLflow 2.x users, there is no backport — you must migrate to 3.x. Per the noisgate remediation SLA for CRITICAL, complete within 90 days. Given KEV listing, federal agencies must remediate by September 2, 2026.- IMDSv2 alone is insufficient. It blocks the AWS IMDS credential-theft path but the SSRF can still reach any internal HTTP service — Redis, Elasticsearch, Docker daemon, internal APIs. The vulnerability is an unrestricted SSRF, not just an IMDS reader.
- TLS termination at a reverse proxy does not help. The SSRF originates from the MLflow server process itself, not from the client connection. Terminating TLS at nginx/ALB does nothing to prevent the server-side redirect following.
- Network segmentation without egress filtering is incomplete. Placing MLflow in a dedicated subnet stops lateral network access but does not block IMDS (which is link-local on the same host) or other services in the same subnet.
- Upgrading to MLflow 3.10.0–3.14.x does not help. The SSRF guard added in 3.10.0 is the exact code being bypassed by this vulnerability. Only 3.15.0+ contains the fix.
The supporting signals.
| In-the-Wild Exploitation | Confirmed and active. WatchTowr detected indiscriminate scanning for exposed MLflow instances within hours of CVE assignment on August 17, 2026. Multiple financially motivated actors and initial access brokers are running automated exploit chains via Tor exit nodes and cloud proxies. CISA added CVE-2026-64849 to the KEV catalog on August 19, 2026 with a federal remediation deadline of September 2, 2026. |
|---|---|
| Proof-of-Concept Availability | Multiple public PoCs. GitHub repos BiuTrap/CVE-2026-64849 and codeb0ssx/CVE-2026-64849-PoC provide working exploit code. The vendor GHSA itself includes a step-by-step PoC workflow. Exploit complexity is trivial — a single curl command. |
| EPSS Score | 0.09839 (9.8%) — above the 95th percentile of all scored CVEs. This places it in the highest-risk band for predicted exploitation probability within 30 days. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N → 9.3 Critical. Network-attackable, no auth, no user interaction, scope change into cloud infra, high confidentiality impact (credential theft), low integrity impact (blind-write variant via 307/308). |
| Affected Versions | All MLflow releases below 3.15.0, including the entire 1.x and 2.x lines. The SSRF guard introduced in 3.10.0 is bypassable. MLflow 2.x (final release 2.22.5, May 12, 2026) is de facto end-of-life and will never receive a backport — migration to 3.15.0+ is the only path. |
| Fixed Version | MLflow 3.15.0 (released July 31, 2026, commit ba94952247, PR #24258). Implements SSRFProtectedHTTPAdapter that validates peer IP at connect() time, covering all redirect targets and DNS rebinding. A follow-up in 3.16.0 (PR #25568) closes IPv6-transition bypasses. |
| Internet Exposure | ~31,773 exposed MLflow Tracking Servers indexed by Shodan/Censys as of June 2026, up 173% from 11,620 a month earlier. MLflow ships with no authentication by default on port 5000. Attackers are using Shodan and Censys to enumerate targets at scale. |
| Reporting Researchers | Originally reported by freeman-bb (private disclosure, June 12, 2026). Independently discovered by AUTHENSOR (public issue #24179, June 26, 2026). Additional finders: y011d4, ibondarenko1, h1-mrz, th3cyb3rc0p. Post-disclosure exploitation tracking by WatchTowr. |
| CERT Advisories | National CERT advisories issued by Germany (BSI), Netherlands (NCSC-NL), Slovakia (SK-CERT), Canada (CCCS), Japan (JPCERT), South Korea (KrCERT), and India (CERT-In). Greenbone OpenVAS Enterprise Feed includes remote detection. |
| MLflow Install Base | 27,000+ GitHub stars, 60+ million monthly PyPI downloads. Primarily deployed as a centralized ML experiment tracking and model registry server in cloud environments (AWS, GCP, Azure). Cloud-hosted instances with IMDS access are the primary exploitation target. |
Sources.
- GitHub Security Advisory GHSA-7gwp-5pfp-969j
- Greenbone — MLflow SSRF Vulnerability CVE-2026-64849 Exploited
- SecurityWeek — MLflow Vulnerability Exploited for Cloud Credential Theft
- The Hacker News — Attackers Exploit MLflow SSRF Flaw to Steal Cloud Credentials
- MLflow Fix PR #24258 — DNS-rebinding SSRF bypass in webhook delivery
- Rescana — Active Exploitation of MLflow SSRF Enables Cloud Credential Theft
- Decryption Digest — MLflow SSRF CVE-2026-64849 Cloud Credential Theft Explained
- EndOfLife.ai — MLflow 2.x EOL and CVE-2026-64849
Why this verdict
- Zero friction on attacker position: The vulnerable endpoint (
POST /api/2.0/mlflow/webhooks/{id}/test) requires no authentication, no special privileges, and no user interaction. This is the lowest possible bar — any host that can reach the MLflow port can exploit it. - Massive internet exposure eliminates reachability friction: Over 31,000 MLflow instances are internet-exposed with no auth gate. MLflow ships unauthenticated by default. This is not a theoretical exposure — attackers are actively scanning and exploiting these instances at scale.
- Trivial exploit with weaponized PoCs: Multiple public PoCs exist, the vendor advisory includes a step-by-step walkthrough, and exploitation was automated within hours of disclosure. The entire chain is a single HTTP POST with a redirect — no memory corruption, no race condition, no complex tooling.
- Role multiplier: MLflow occupies the CI/CD + artifact stores tier from the high-value-role catalog. In production deployments, MLflow Tracking Server is the centralized model registry and experiment orchestration layer — analogous to an artifact repository. (a) *Dev sandbox*: local
mlflow uion a laptop — SSRF impact is limited to localhost services, LOW. (b) *Typical role*: team tracking server in a cloud VPC — SSRF reaches IMDS, credential theft gives access to the team's cloud resources, HIGH. (c) *High-value role*: production ML pipeline hub with IAM roles granting access to training data lakes, model artifact buckets, secrets managers, and compute orchestration — credential theft yields cloud-tenant-scale blast radius including supply-chain poisoning of ML models. A significant share of MLflow's 60M+ monthly downloads serve production ML platforms in cloud environments. The high-value role outcome (cloud account takeover, model supply-chain poisoning) with ≥10% of installs in that role floors the verdict at CRITICAL. - KEV-listed with active exploitation: CISA added this to KEV within 48 hours of disclosure. WatchTowr confirmed automated exploitation campaigns by financially motivated actors. This is not a theoretical risk — it is an active incident.
Why not higher?
The score is already at 9.3 CRITICAL, near the ceiling for a confidentiality-focused vulnerability without direct code execution. A 10.0 would require high integrity AND availability impact (e.g., full RCE). The blind-write variant (307/308) adds limited integrity impact but does not achieve arbitrary code execution on the MLflow host itself.
Why not lower?
Every friction-reducing factor aligns in the attacker's favor: unauthenticated, internet-exposed by default, trivial to exploit, actively exploited, public PoCs, KEV-listed, and the blast radius in cloud deployments is tenant-scale credential theft. The only theoretical friction (auth plugins, IMDSv2, VPN) represents the exception rather than the norm across the 31,000+ exposed instances. Dropping below CRITICAL would require ignoring confirmed active exploitation and the default-unauthenticated deployment model.
Crowdsourced verification payload.
Run this script on each host running an MLflow Tracking Server (or against your Python environment where mlflow is installed). Requires read access to the pip or mlflow binary — no root needed. Invoke with: bash check_cve_2026_64849.sh
#!/usr/bin/env bash
# check_cve_2026_64849.sh
# Checks whether the installed MLflow version is vulnerable to CVE-2026-64849
# (unauthenticated SSRF via webhook redirect bypass)
# Fixed in: MLflow 3.15.0
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
FIXED_MAJOR=3
FIXED_MINOR=15
FIXED_PATCH=0
# Try to get MLflow version
if command -v mlflow &>/dev/null; then
VERSION=$(mlflow --version 2>/dev/null | grep -oP '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
elif command -v pip &>/dev/null; then
VERSION=$(pip show mlflow 2>/dev/null | grep -i '^Version:' | grep -oP '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
elif command -v pip3 &>/dev/null; then
VERSION=$(pip3 show mlflow 2>/dev/null | grep -i '^Version:' | grep -oP '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
else
echo "UNKNOWN - mlflow and pip not found in PATH"
exit 2
fi
if [ -z "${VERSION:-}" ]; then
echo "UNKNOWN - could not determine MLflow version"
exit 2
fi
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
echo "Detected MLflow version: $VERSION"
echo "Fixed version: ${FIXED_MAJOR}.${FIXED_MINOR}.${FIXED_PATCH}"
if [ "$MAJOR" -gt "$FIXED_MAJOR" ]; then
echo "PATCHED - MLflow $VERSION >= 3.15.0"
exit 0
elif [ "$MAJOR" -eq "$FIXED_MAJOR" ] && [ "$MINOR" -gt "$FIXED_MINOR" ]; then
echo "PATCHED - MLflow $VERSION >= 3.15.0"
exit 0
elif [ "$MAJOR" -eq "$FIXED_MAJOR" ] && [ "$MINOR" -eq "$FIXED_MINOR" ] && [ "$PATCH" -ge "$FIXED_PATCH" ]; then
echo "PATCHED - MLflow $VERSION >= 3.15.0"
exit 0
else
echo "VULNERABLE - MLflow $VERSION < 3.15.0 is affected by CVE-2026-64849"
if [ "$MAJOR" -lt 3 ]; then
echo "WARNING: MLflow ${MAJOR}.x is end-of-life. No backport exists. Migration to 3.15.0+ required."
fi
exit 1
fi