Skip to content

Astro vulnerable to URL manipulation via headers, leading to middleware and CVE-2025-61925 bypass

Moderate severity GitHub Reviewed Published Nov 13, 2025 in withastro/astro • Updated Nov 13, 2025

Package

npm astro (npm)

Affected versions

>= 2.16.0, < 5.15.5

Patched versions

5.15.5

Description

Summary

In impacted versions of Astro using on-demand rendering, request headers x-forwarded-proto and x-forwarded-port are insecurely used, without sanitization, to build the URL. This has several consequences the most important of which are:

  • Middleware-based protected route bypass (only via x-forwarded-proto)
  • DoS via cache poisoning (if a CDN is present)
  • SSRF (only via x-forwarded-proto)
  • URL pollution (potential SXSS, if a CDN is present)
  • WAF bypass

Details

The x-forwarded-proto and x-forwarded-port headers are used without sanitization in two parts of the Astro server code. The most important is in the createRequest() function. Any configuration, including the default one, is affected:

https://github.com/withastro/astro/blob/970ac0f51172e1e6bff4440516a851e725ac3097/packages/astro/src/core/app/node.ts#L97
https://github.com/withastro/astro/blob/970ac0f51172e1e6bff4440516a851e725ac3097/packages/astro/src/core/app/node.ts#L121

These header values are then used directly to construct URLs.

By injecting a payload at the protocol level during URL creation (via the x-forwarded-proto header), the entire URL can be rewritten, including the host, port and path, and then pass the rest of the URL, the real hostname and path, as a query so that it doesn't affect (re)routing.

If the following header value is injected when requesting the path /ssr:

x-forwarded-proto: https://www.malicious-url.com/?tank=

The complete URL that will be created is: https://www.malicious-url.com/?tank=://localhost/ssr

As a reminder, URLs are created like this:

url = new URL(`${protocol}://${hostnamePort}${req.url}`);

The value is injected at the beginning of the string (${protocol}), and ends with a query ?tank= whose value is the rest of the string, ://${hostnamePort}${req.url}.

This way there is control over the routing without affecting the path, and the URL can be manipulated arbitrarily. This behavior can be exploited in various ways, as will be seen in the PoC section.

The same logic applies to x-forwarded-port, with a few differences.

Note

The createRequest function is called every time a non-static page is requested. Therefore, all non-static pages are exploitable for reproducing the attack.

PoC

The PoC will be tested with a minimal repository:

  • Latest Astro version at the time (2.16.0)
  • The Node adapter
  • Two simple pages, one SSR (/ssr), the other simulating an admin page (/admin) protected by a middleware
  • A middleware example copied and pasted from the official Astro documentation to protect the admin page based on the path

Download the PoC repository

Middleware-based protected route bypass - x-forwarded-proto only

The middleware has been configured to protect the /admin route based on the official documentation:

// src/middleware.ts
import { defineMiddleware } from "astro/middleware";

export const onRequest = defineMiddleware(async (context, next) => {
  const isAuthed = false; // auth logic
  if (context.url.pathname === "/admin" && !isAuthed) {
    return context.redirect("/");
  }
  return next();
});
  1. When tryint to access /admin the attacker is naturally redirected :

    curl -i http://localhost:4321/admin
    image
  2. The attackr can bypass the middleware path check using a malicious header value:

    curl -i -H "x-forwarded-proto: x:admin?" http://localhost:4321/admin
    image

How ​​is this possible?

Here, with the payload x:admin?, the attacker can use the URL API parser to their advantage:

  • x: is considered the protocol
  • Since there is no //, the parser considers there to be no authority, and everything before the ? character is therefore considered part of the path: admin

During a path-based middleware check, the path value begins with a /: context.url.pathname === "/admin". However, this is not the case with this payload; context.url.pathname === "admin", the absence of a slash satisfies both the middleware check and the router and consequently allows us to bypass the protection and access the page.

SSRF

As seen, the request URL is built from untrusted input via the x-forwarded-protocol header, if it turns out that this URL is subsequently used to perform external network calls, for an API for example, this allows an attacker to supply a malicious URL that the server will fetch, resulting in server-side request forgery (SSRF).

Example of code reusing the "origin" URL, concatenating it to the API endpoint :

image

DoS via cache poisoning

If a CDN is present, it is possible to force the caching of bad pages/resources, or 404 pages on the application routes, rendering the application unusable.

A 404 cab be forced, causing an error on the /ssr page like this : curl -i -H "x-forwarded-proto: https://localhost/vulnerable?" http://localhost:4321/ssr
image

Same logic applies to x-forwarded-port : curl -i -H "x-forwarded-port: /vulnerable?" http://localhost:4321/ssr

How ​​is this possible?

The router sees the request for the path /vulnerable, which does not exist, and therefore returns a 404, while the potential CDN sees /ssr and can then cache the 404 response, consequently serving it to all users requesting the path /ssr.

URL pollution

The exploitability of the following is also contingent on the presence of a CDN, and is therefore cache poisoning.

If the value of request.url is used to create links within the page, this can lead to Stored XSS with x-forwarded-proto and the following value:

x-forwarded-proto: javascript:alert(document.cookie)//

results in the following URL object:

image

It is also possible to inject any link, always, if the value of request.url is used on the server side to create links.

x-forwarded-proto: https://www.malicious-site.com/bad?

The attacker is more limited with x-forwarded-port

If the value of request.url is used to create links within the page, this can lead to broken links, with the header and the following value:

X-Forwarded-Port: /nope?

Example of an Astro website:
Capture d’écran 2025-11-03 à 22 07 14

WAF bypass

For this section, Astro invites users to read previous research on the React-Router/Remix framework, in the section "Exploitation - WAF bypass and escalations". This research deals with a similar case, the difference being that the vulnerable header was x-forwarded-host in their case:

https://zhero-web-sec.github.io/research-and-things/react-router-and-the-remixed-path

Note: A section addressing DoS attacks via cache poisoning using the same vector was also included there.

CVE-2025-61925 complete bypass

It is possible to completely bypass the vulnerability patch related to the X-Forwarded-Host header.

By sending x-forwarded-host with an empty value, the forwardedHostname variable is assigned an empty string. Then, during the subsequent check, the condition fails because forwardedHostname returns false, its value being an empty string:

if (forwardedHostname && !App.validateForwardedHost(...))

Consequently, the implemented check is bypassed. From this point on, since the request has no host (its value being an empty string), the path value is retrieved by the URL parser to set it as the host. This is because the http/https schemes are considered special schemes by the WHATWG URL Standard Specification, requiring an authority state.

From there, the following request on the example SSR application (astro repo) yields an SSRF:
Capture d’écran 2025-11-06 à 21 18 26
empty x-forwarded-host + the target host in the path

Credits

  • Allam Rachid (zhero;)
  • Allam Yasser (inzo)

References

@matthewp matthewp published to withastro/astro Nov 13, 2025
Published by the National Vulnerability Database Nov 13, 2025
Published to the GitHub Advisory Database Nov 13, 2025
Reviewed Nov 13, 2025
Last updated Nov 13, 2025

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
Low
Availability
Low

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L

EPSS score

Exploit Prediction Scoring System (EPSS)

This score estimates the probability of this vulnerability being exploited within the next 30 days. Data provided by FIRST.
(11th percentile)

Weaknesses

Server-Side Request Forgery (SSRF)

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination. Learn more on MITRE.

CVE ID

CVE-2025-64525

GHSA ID

GHSA-hr2q-hp5q-x767

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.