GitHub Advisoryadvisory · npm
GHSA-hr2q-hp5q-x767 Astro vulnerable to URL manipulation via headers, leading to middleware and CVE-2025-61925 bypass
MEDIUM
Official page ↗
- Aliases
- CVE-2025-64525
- Ecosystem
- npm
- Packages
- astro
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L
- Weaknesses
- CWE-918
- Reviewed
- 2025-11-13
Affected packages
• npm astro: >= 2.16.0, < 5.15.5 → fixed in 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:
```ts
// 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 :
```sh
curl -i http://localhost:4321/admin
```
2. The attackr can bypass the middleware path check using a malicious header value:
```sh
curl -i -H "x-forwarded-proto: x:admin?" http://localhost:4321/admin
```
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 :
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`
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:
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:
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…
Related
Alias · CVE (1)
- CVE-2025-64525 Astro is a web framework. vulnerability
Weakness · CWE (1)
- CWE-918 Server-Side Request Forgery weakness
References
- https://github.com/withastro/astro/security/advisories/GHSA-hr2q-hp5q-x767 web
- https://nvd.nist.gov/vuln/detail/CVE-2025-64525 advisory
- https://github.com/withastro/astro/commit/dafbb1ba29912099c4faff1440033edc768af8b4 web
- https://github.com/withastro/astro package
- https://github.com/withastro/astro/blob/970ac0f51172e1e6bff4440516a851e725ac3097/packages/astro/src/core/app/node.ts#L121 web
- https://github.com/withastro/astro/blob/970ac0f51172e1e6bff4440516a851e725ac3097/packages/astro/src/core/app/node.ts#L97 web