Penetration Testing

Validate-Then-Fetch SSRF: MLflow CVE-2026-64849

MLflow CVE-2026-64849 shows how validate-then-fetch turns into a TOCTOU SSRF once your HTTP client follows redirects, and the test case that finds it.

RG&AK
Rathnakara GN & Ashok Kamat
Cybersecify
14 min read

MLflow CVE-2026-64849 carries a CVSS v3.1 base score of 9.3, sits in CISA’s Known Exploited Vulnerabilities catalog, and was published by India’s CERT-In as vulnerability note CIVN-2026-0416 on 20 August 2026. The part worth your time is not MLflow. It is the shape of the bug. A URL was validated once, as a string, and then handed to an HTTP client that followed a redirect and resolved the hostname again. The check and the use looked at two different destinations. If your product accepts a URL from a user and fetches it later, for a webhook, a callback, a link preview, an image import or a PDF render, you have this shape in your codebase, and the only question that matters is whether you pin the address you validated or whether you validate text and let the client go wherever it is sent.

Key findings

  • Validate-then-fetch is a time-of-check-to-time-of-use bug, not a validation bug. The validator was correct about the string it inspected. It was never consulted about the address the socket actually reached.
  • The published record names two different files, which is the whole story. Per NVD, _validate_webhook_url() in mlflow/utils/validation.py checked the original URL, while mlflow/webhooks/delivery.py followed redirects and re-resolved the hostname without pinning the validated address.
  • This is a web SSRF in a machine learning platform, not an AI vulnerability. No model, no prompt, no inference path appears anywhere in the bug. It is AI and ML infrastructure, and it is found with ordinary web and API test cases.
  • The matching test case is public and already written: OWASP WSTG v4.2, WSTG-INPV-19. Its Common Filter Bypass section names registering a domain that resolves to 127.0.0.1, and its reference list includes an item titled Abusing the AWS Metadata Service Using SSRF Vulnerabilities.
  • CERT-In published this as CIVN-2026-0416 on 20 August 2026 at Critical severity. For Indian teams that is a nationally recognised reference to attach to an out-of-cycle upgrade request.

This post is written by Rathnakara GN, who leads penetration testing at Cybersecify, with Ashok Kamat. It analyses a publicly disclosed issue using the published record. We did not test MLflow, we have no client relationship to this software, and nothing here is reproduction detail. The endpoint path and the validator function name appear below because both are already in the CVE record and the vendor advisory, and a defender cannot check their own code against a pattern they are not allowed to see named.

What the record actually says

The primary sources agree, which is worth noting because they were written by different parties.

MLflow shipped version 3.15.0 on 31 July 2026. Its release notes contain the line “[Model Registry] Fix DNS-rebinding SSRF bypass in webhook delivery (#24258, @PattaraS)”.

The vendor advisory is GHSA-7gwp-5pfp-969j, titled “Unauthenticated full-read SSRF in MLflow webhook delivery: _validate_webhook_url bypassed via unvalidated HTTP redirects (and DNS rebinding)”. It rates the issue Critical and names the affected range as everything before 3.15.0.

The NVD entry for CVE-2026-64849 was published on 17 August 2026. Its description reads: “MLflow is an open source AI engineering platform for agents, large language models, and machine learning models. Prior to 3.15.0, the unauthenticated POST /api/2.0/mlflow/webhooks/{id}/test endpoint calls _validate_webhook_url() in mlflow/utils/validation.py only for the original URL while mlflow/webhooks/delivery.py follows redirects and re-resolves the hostname without pinning the validated address, allowing attackers to reach internal or cloud metadata services and receive response_status and response_body.” The score is 9.3 Critical, vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N, assigned by GitHub as the CVE Numbering Authority, and the weakness is CWE-918, Server-Side Request Forgery.

CISA added it to the Known Exploited Vulnerabilities catalog on 19 August 2026 under the name “MLflow Server-Side Request Forgery Vulnerability”, with a remediation due date of 2 September 2026 for the agencies that catalog binds. CISA’s own one-line description: “MLflow contains a server-side request forgery vulnerability that can allow attackers to reach internal or cloud metadata services and receive response_status and response_body.” The catalog records known ransomware campaign use as Unknown.

On exploitation in the wild, the attribution is single-origin and we are going to keep it that way. The security firm watchTowr stated that within hours of CVE assignment, watchTowr Intel via Attacker Eye, its global honeypot network, observed attackers targeting cloud-hosted MLflow systems in an attempt to extract credentials and secrets. Several newsrooms carried that statement. They are reporting the same observation from the same source, so treat it as one line of evidence, corroborated structurally by the fact that CISA placed the CVE in a catalog reserved for vulnerabilities with evidence of active exploitation.

Two claims that circulated in secondary coverage, about post-compromise cryptomining and the creation of cloud identities for persistence, we could not trace to a primary source, so they are not in this post.

Why validate-then-fetch is a TOCTOU bug

Read the mechanism with the product name removed and it stops looking like an MLflow problem.

A caller supplies a URL. A function inspects that URL, resolves the hostname, sees a public address, and returns true. The URL string is then passed to an HTTP client. The client parses the string, resolves the hostname itself, opens a connection, and follows whatever redirects the server sends back, resolving each new hostname in turn.

Nothing in that sequence carries the approved address forward. The validator produced a boolean about a string. The client made an independent decision about where to connect. The two are joined only by the text of the URL, and the text of a URL is not a destination. It is a name that gets turned into a destination, twice, by two different pieces of code, at two different moments.

That is a textbook time-of-check-to-time-of-use flaw, and naming it correctly is what makes the fix obvious. The classic TOCTOU examples in security training are filesystem races, where a program checks a path and then opens it, and the path is swapped in between. This is the same defect with DNS and HTTP redirects playing the role of the swap. Two independent mechanisms can perform the swap: an attacker-controlled server can answer with a redirect to an internal address, or an attacker-controlled DNS record can return a public address on the first lookup and an internal one on the second. The MLflow advisory names both.

The reason this survives code review is that both halves look correct in isolation. The validator is a tidy function with a clear name and a unit test. The delivery path is three lines of a standard HTTP library with sensible defaults, and following redirects is the default in most HTTP clients precisely because most callers want it. The defect lives in the space between two reasonable pieces of code, which is exactly the space that a review of either file will not cover.

The category error worth avoiding

Vulnerability databases describe MLflow, in the first sentence of the CVE record, as “an open source AI engineering platform for agents, large language models, and machine learning models.”

That sentence will push a reader toward filing this under an AI risk taxonomy, and it should not. Walk the bug and count the AI: there is no model, no prompt, no inference call, no training data, no embedding store and no agent anywhere in the path. There is a webhook feature, a URL string, a validator, an HTTP client and a redirect. Those are the constituent parts of a web application from 2010.

The useful distinction for a CTO deciding where testing budget goes is between the model layer and the infrastructure around it. The model layer is where prompt handling, output handling, tool invocation and data flow into and out of a model live, and that layer genuinely needs test cases written for it, which is what an AI application pentest is for. Everything else in an ML stack is a tracking server, a registry, an artifact store, a scheduler, a queue, a set of APIs and a pile of webhooks. That is API and cloud surface, and it is tested with API and cloud test cases.

Getting this backwards costs money in both directions. Buying model-layer testing for a tracking server produces a report about a threat model the component does not have. Assuming that because a system is “AI” it needs only AI testing leaves the ordinary web surface, which is where this 9.3 lived, untested. The MLflow issue is a clean argument for scoping AI systems as what they mostly are, which is a lot of conventional infrastructure with a model somewhere inside it.

Where this shape lives in an ordinary SaaS product

The shape is not rare. Any feature where a user supplies a URL that your servers later fetch has it. The recurring ones in a Series A SaaS product:

  • Outbound webhooks and callback URLs. The customer types a URL, you POST to it, and there is usually a “send test event” button that returns the response so the customer can debug. That button converts a blind primitive into a full read.
  • Link previews and unfurls. Anywhere a pasted URL becomes a card with a title and an image.
  • Import by URL. Avatars, logos, CSV imports, document imports, “add from URL” in any uploader.
  • HTML to PDF and screenshot rendering. The renderer is an HTTP client with a browser attached, and it will fetch subresources you did not think about.
  • OAuth, OIDC and SAML metadata discovery. Tenant-supplied issuer URLs, JWKS endpoints and metadata documents are all fetched server side.
  • RSS, sitemap and feed importers, and any integration that polls a customer-supplied endpoint.
  • Server-side proxies for third-party APIs, where the customer configures the base URL.

The severity multiplier is the environment. A workload running in a cloud instance sits next to a metadata service on a link-local address that hands out credentials for the workload’s identity to anything that asks from the right network position. That is why the CVSS vector for this issue has scope changed and confidentiality high: the vulnerable component and the impacted component are not the same thing. The webhook feature was the way in. The cloud identity was the prize.

What to check in your own code

Work through this in order. It does not require knowing anything about MLflow.

1. Build the inventory. Grep for the HTTP client constructors in your language, then trace backwards from each call site to see whether the URL, or any part of it, can be influenced by a value that arrived from outside your trust boundary. Include values that came from a database, if the database got them from a user. Most teams find more call sites than they expected, and the surprising ones are usually in a background worker rather than in the request path.

2. For each one, answer the redirect question. Does the client follow redirects? In most standard libraries the answer is yes by default. If it does, is every hop validated with the same rules as the first, or only the first?

3. Answer the pinning question. This is the one that decides the bug. Between the moment your validator approved an address and the moment the socket connected, was the hostname resolved again? If the answer is yes, or if you do not know, your validation is advisory rather than binding. The correct pattern is to resolve once, make the decision about the resolved IP addresses, and then connect to a chosen address directly, so that no second resolution can occur.

4. Answer the reflection question. How much of the upstream response reaches the caller? Status code, body, headers, response time and error message text are all channels. A feature that returns the body is a full-read primitive. A feature that returns only a boolean success flag still leaks internal network structure through timing and error differences, which is the blind variant.

5. Check the allowlist direction. A denylist of internal ranges is the wrong shape, because the space of ways to express an internal address is large and the space of destinations your product is supposed to reach is small. An allowlist of permitted hosts, resolved and pinned, is the shape that holds.

6. Check egress at the network layer. Application-level validation is one control. The workload’s own egress policy is a second, independent one, and it is the one that still works when the first has a bug. A tracking server has no legitimate reason to open a connection to a link-local metadata address. Where your cloud provider offers a stricter metadata service mode that requires a session token, turn it on.

Controls 3 and 6 are the ones that survive a refactor. Controls written as string checks tend to decay, because the next engineer adds a feature, uses a different HTTP client, and the check does not travel with it.

The test case, and the honest limit of the mapping

The test case for this class is not new and not ours. It is OWASP Web Security Testing Guide v4.2, test case WSTG-INPV-19, Testing for Server-Side Request Forgery, and it is worth ninety seconds of a buyer’s time to open it and read two things.

The first is its Common Filter Bypass section, which is a list of ways an attacker gets a validator to approve a destination it should have rejected. It covers alternative encodings of a loopback address, URL parser confusion using the userinfo separator and the fragment character, and, most directly relevant here, “Registering your own domain that resolves to 127.0.0.1”. That is the same manoeuvre as a validated public hostname whose second resolution, or whose redirect target, points inward. The failure mode was documented in a public testing guide before the CVE existed.

The second is its References list, which includes an item titled “Abusing the AWS Metadata Service Using SSRF Vulnerabilities”. That is the attack path CISA describes in its own catalog entry for this CVE, word for word in substance: reach internal or cloud metadata services and receive the response.

So the mapping is unusually easy to verify. The weakness class in the CVE record is CWE-918. The OWASP test case for CWE-918 is WSTG-INPV-19. That test case’s own documentation names both the bypass technique and the target. You do not have to take a vendor’s word for the mapping, which is the point of naming it this precisely. Our methodology page lists the standards our web and API testing works against, WSTG v4.2 among them.

The limit, stated in the same breath as the coverage, because it belongs there. Running WSTG-INPV-19 against an application is not a guarantee of finding every instance of this pattern in a large codebase. Outbound-fetch call sites hide in background workers, in third-party SDKs and in features that are not reachable from the UI, and no time-boxed engagement enumerates all of them with certainty. What is certain is the other direction: a scope that excludes outbound-fetch features cannot find this at all. That is the practical reason to name webhooks, callbacks, imports and rendering surfaces explicitly when you write a scope, rather than assuming the word “application” covers them.

The India angle: CERT-In CIVN-2026-0416

CERT-In published vulnerability note CIVN-2026-0416, “Server-Side Request Forgery Vulnerability in MLflow”, with an original issue date of 20 August 2026 and a severity rating of Critical. It lists MLflow versions prior to 3.15.0 as affected. Its overview states that a vulnerability in MLflow enables unauthenticated attackers to execute Server-Side Request Forgery attacks and retrieve sensitive server information, and its description quotes the impact as follows: “Successful exploitation of this vulnerability could allow an unauthenticated attacker to conduct Server-Side Request Forgery (SSRF) attacks and gain sensitive information on the targeted server.” The solution it gives is to apply the vendor update, and it points at the GitHub advisory.

Two things to be clear about. This is India’s national CERT publishing on a globally used open source product, not an India breach and not an India-specific incident. And the note adds no technical detail that the vendor advisory does not already carry.

What it does add is standing. If you run MLflow inside an Indian company and you are trying to get an out-of-cycle upgrade past a change advisory board, a CERT-In vulnerability note is a reference your risk, audit and compliance functions already recognise, and it is shorter to cite than a GitHub advisory URL. That is a small thing that saves a real argument.

We are not CERT-In empanelled and do not claim to be. We cite CERT-In as a primary source the same way we cite NVD or OWASP.

Scoping this, if you are buying testing

If you want this class covered, the scope needs to say so. “Test our web app” does not reliably reach a webhook delivery worker. A scope that names the outbound-fetch features, the webhook sender, the import paths, the renderer, the metadata discovery endpoints, is what puts them in front of a tester.

Our Startup Pentest is INR 74,999 and covers one scope over 5 business days. The Growth Pentest is INR 1,79,999 and covers two scopes over 10 business days, with SOC 2 and ISO 27001 evidence mapping. Scopes run sequentially by default, so each additional scope adds 5 business days. Parallel testing is available on request from the third scope onward on Growth, and it is an option rather than a guarantee.

For an ML platform specifically, the honest scoping conversation is usually two scopes rather than one: the application and its APIs, including every outbound-fetch feature, and the cloud environment the workload runs in, because the second is what determines whether an SSRF finding is a curiosity or a credential disclosure. Our API pentest and cloud pentest pages describe what each covers.

The one sentence to take away

Validation that returns a verdict about a string is not validation. Validation that returns an address, and a client that connects to that address and nothing else, is.

Want to see how we report AI findings? Our sample penetration test report is published in full, no email gate. For what an AI scope covers and where it stops, see AI and API penetration testing.

Sources

All URLs and figures were checked against the sources named above on 5 September 2026.

Key takeaways, in one page

A single-page summary of this article. Free to share, repost or put in a deck. We only ask that the link stays on it.

One-page key takeaways from Validate-Then-Fetch SSRF: MLflow CVE-2026-64849

Frequently Asked Questions

What is CVE-2026-64849 in plain terms?

It is a server-side request forgery issue in MLflow, an open source platform for managing the machine learning lifecycle. The National Vulnerability Database describes it as follows: prior to 3.15.0, the unauthenticated POST /api/2.0/mlflow/webhooks/{id}/test endpoint calls _validate_webhook_url() in mlflow/utils/validation.py only for the original URL while mlflow/webhooks/delivery.py follows redirects and re-resolves the hostname without pinning the validated address, allowing attackers to reach internal or cloud metadata services and receive response_status and response_body. It carries a CVSS v3.1 base score of 9.3 with the vector AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N, assigned by GitHub as the CVE Numbering Authority, and is classified as CWE-918. The record was published on 17 August 2026 and the fix shipped in MLflow 3.15.0 on 31 July 2026.

Why is this called a time-of-check-to-time-of-use bug rather than a validation bug?

Because the validator was not wrong about the thing it looked at. It looked at the URL string the caller supplied and correctly concluded that the host was public. The failure is that the value it approved was never the value that got used. Between the check and the connection, an HTTP client followed a redirect and resolved a hostname again, and the second resolution produced a different destination. That gap between what was checked and what was used is the definition of a time-of-check-to-time-of-use flaw. It matters because it changes the fix. Teams that read this as a validation bug write a better blocklist, which does nothing, since the blocklist is still evaluated against a string that the client is free to leave behind. Teams that read it as a TOCTOU flaw carry the resolved address forward into the socket, which closes it.

Is this an AI or LLM vulnerability because it was found in an ML platform?

No, and the distinction is worth getting right because it decides which testing you buy. Vulnerability databases describe MLflow as an open source AI engineering platform for agents, large language models, and machine learning models, and that sentence tempts people to file the issue under an AI risk taxonomy. There is no model in this bug. There is no prompt, no inference call, no training data and no agent. There is a webhook feature, a URL validator, an HTTP client and a redirect. Every one of those is ordinary web and API surface, and the test case that finds it is an ordinary web and API test case. The correct framing is that this is AI and ML infrastructure, and infrastructure gets tested with infrastructure test cases. Reserve model-specific testing for the parts of your stack that actually contain a model.

Which OWASP test case covers this?

OWASP Web Security Testing Guide v4.2, test case WSTG-INPV-19, Testing for Server-Side Request Forgery. Two things on that page make it a strong mapping and both are checkable in under a minute. Its Common Filter Bypass section names the exact evasion family this bug belongs to, including registering your own domain that resolves to 127.0.0.1, which is the same trick as pointing a validated public hostname at an internal address. Its reference list includes an item titled Abusing the AWS Metadata Service Using SSRF Vulnerabilities, which is the attack path CISA describes in its catalog entry. The classification in the CVE record is CWE-918, Server-Side Request Forgery, which is the weakness that test case exists to find.

Our product has webhooks. What should we check first?

Find every place in your codebase where a URL that originated outside your trust boundary is later fetched by your own infrastructure, then answer three questions for each one. First, does the HTTP client follow redirects, and if it does, is the destination of each hop validated as strictly as the first one. Second, is the IP address that the validator resolved and approved the same address the socket eventually connects to, or does the stack resolve the hostname again at connection time. Third, does any part of the response, including the status code, the body, the headers, the timing or the error text, get returned to the caller. The third question decides whether an attacker gets a blind primitive or a full read, and a full read of a cloud metadata endpoint is a credential disclosure. Outbound webhooks are the obvious case. Link previews, avatar and logo imports by URL, PDF and screenshot rendering, OAuth and OIDC discovery documents, RSS and sitemap importers, and file imports by URL all have the same shape.

Does patching MLflow close the class, or only the instance?

Only the instance. Upgrading to MLflow 3.15.0 or later fixes that specific endpoint in that specific product, and if you run MLflow you should do it, because CISA placed the CVE in its Known Exploited Vulnerabilities catalog on 19 August 2026 with a remediation due date of 2 September 2026 for the agencies it binds. The class is the pattern, and the pattern lives in your own code. A single validator that returns a boolean about a string, followed by a client that resolves the hostname again, will produce the same outcome in any language and any framework. The durable fix is architectural: resolve once, decide on the resolved address, then connect to that address rather than to the name, and refuse redirects or re-run the full decision on every hop.

Has India's national CERT published on this?

Yes. CERT-In issued vulnerability note CIVN-2026-0416, Server-Side Request Forgery Vulnerability in MLflow, with an original issue date of 20 August 2026 and a severity rating of Critical. It lists MLflow versions prior to 3.15.0 as affected and states that successful exploitation of this vulnerability could allow an unauthenticated attacker to conduct Server-Side Request Forgery (SSRF) attacks and gain sensitive information on the targeted server. Its stated solution is to apply the vendor update. This is India's national CERT publishing on a globally used open source product, not an India-specific incident, and it is useful to Indian teams for a practical reason: a CERT-In note is a reference that internal risk and audit functions in India already recognise, so it shortens the argument for an out-of-cycle upgrade.

How would a penetration test surface this class of finding?

By enumerating every outbound-fetch feature in the application, then testing each one against the redirect and re-resolution boundary rather than only against the initial URL. In practice that means listing the endpoints that accept a URL, checking whether the client follows redirects, checking whether the address approved at validation is the address used at connection time, and checking how much of the upstream response comes back to the caller. That work sits inside a web application or API scope, and the WSTG v4.2 test case for it is WSTG-INPV-19. The honest limit is worth stating in the same breath as the coverage: running the test case is not a guarantee of finding every instance in a large codebase, but a scope that excludes outbound-fetch features cannot find it at all. That is the practical reason to name webhook and callback surfaces explicitly when you scope, rather than assuming they are covered by the word application.

Security questions, worries, or not sure what to use?

Cybersecify is a founder-led penetration testing firm for AI and SaaS startups. Tell us what you are weighing and we will give you a straight answer. Ask the team or book a free 30-minute call.

Share this article
SSRFAPI PentestCloud PentestWebhooksBreach AnalysisPenetration TestingCERT-In

Spotted something wrong on this page? Facts change and we get things wrong. Tell us and we will check it. We publish corrections on the page rather than editing quietly.