CVE-2025-64525: Astro Astro Vulnerable To URL Manipulation Vulnerability

MODERATE
Advisory Identifier: GHSA-hr2q-hp5q-x767
CVSS 6.5
CWE-918
Published
Advisory Summary

CVE-2025-64525 is a MODERATE (CVSS 6.5) astro vulnerable to url manipulation vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting Astro libraries in the npm ecosystem. The issue involves astro vulnerable to url manipulation via headers, leading to middleware and cve-2025-61925 bypass. Remediation is available by upgrading affected packages to version 5.15.5.

CVE-2025-64525 at a Glance

CVE Identifier CVE-2025-64525
GHSA Identifier GHSA-hr2q-hp5q-x767
Severity MODERATE
CVSS v3.1 Base Score 6.5
CWE Classification CWE-918: Server-Side Request Forgery (SSRF)
Affected Product Astro
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 6.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity Low (I:L)
Availability Low (A:L)

How to Fix CVE-2025-64525

To remediate this vulnerability, upgrade all affected Astro dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

astro fixed: 5.15.5
npm install astro@5.15.5
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls astro

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2025-64525, inspect your project dependencies using npm:

npm ls astro

Analyze the terminal output to locate any version of Astro in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 5.15.5), your application is affected.

Note: The npm ls astro command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where 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. In Astro, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary In impacted versions of Astro using [on-demand rendering](https://docs.astro.build/en/guides/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#L97) [https://github.com/withastro/astro/blob/970ac0f51172e1e6bff4440516a851e725ac3097/packages/astro/src/core/app/node.ts#L121](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](https://github.com/zhero-web-sec/astro-app) ### Middleware-based protected route bypass - x-forwarded-proto only The middleware has been configured to protect the `/admin` route based on [the official documentation](https://docs.astro.build/en/guides/authentication/): ```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 ``` <img width="620" height="102" alt="image" src="https://github.com/user-attachments/assets/15a7bffc-ee56-4ed9-84b2-091cf4d78351" /> 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 ``` <img width="1348" height="159" alt="image" src="https://github.com/user-attachments/assets/d9d9ac1a-5efa-452b-981e-efea8a08d089" /> #### 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 : <img width="601" height="418" alt="image" src="https://github.com/user-attachments/assets/9c374b2c-841c-48d6-98f1-3b3f5b060802" /> ### 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` <img width="998" height="108" alt="image" src="https://github.com/user-attachments/assets/4bab58e5-3045-4e25-9aa2-2f72a0832d86" /> 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: <img width="444" height="202" alt="image" src="https://github.com/user-attachments/assets/c2990626-da5b-4868-9093-dbb9b34780ba" /> 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: <img width="1627" height="298" alt="Capture d’écran 2025-11-03 à 22 07 14" src="https://github.com/user-attachments/assets/02de5e67-f48d-4bf4-810d-6b0714ad2c12" /> ### 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](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](https://github.com/withastro/astro/blob/7a5f28006e9b1f6ad77c7884991ba551ca9ff35b/packages/astro/src/core/app/node.ts#L107), 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](https://url.spec.whatwg.org/#scheme-state), requiring an `authority state`. From there, the following request on the example SSR application (astro repo) yields an SSRF: <img width="1878" height="456" alt="Capture d’écran 2025-11-06 à 21 18 26" src="https://github.com/user-attachments/assets/c5cca89c-9c65-46f6-bf70-cd7a90a9e0d9" /> *empty `x-forwarded-host` + the target `host` in the path* ## Credits - Allam Rachid ([zhero;](https://zhero-web-sec.github.io/research-and-things/)) - Allam Yasser (inzo)

CVE-2025-64525 Affected Astro Versions

1 component(s)
Fixed Versions:
5.15.5
Product / Package Ecosystem Affected Versions Fixed Version
astro
npm
≥ 2.16.0
< 5.15.5
5.15.5

References

Frequently Asked Questions

What is CVE-2025-64525?
CVE-2025-64525 is a security vulnerability in Astro involving Astro vulnerable to URL manipulation via headers, leading to middleware and CVE-2025-61925 bypass under CWE-918: Server-Side Request Forgery (SSRF).
Which Astro versions are affected?
Affected versions include releases prior to 5.15.5 across affected packages: astro.
What version fixes CVE-2025-64525?
Upgrading to 5.15.5 (or the latest patched release for your respective branch) resolves the vulnerability.
Is astro affected?
Yes, astro is affected in releases prior to 5.15.5. Upgrade your dependency to 5.15.5 or later.
What is the CVSS score of CVE-2025-64525?
CVE-2025-64525 has a CVSS v3.1 base score of 6.5 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L.
What CWE is associated with CVE-2025-64525?
CVE-2025-64525 is classified under CWE-918: 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.).
How can I check npm dependencies for CVE-2025-64525?
Run the dependency tree command: 'npm ls astro'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-hr2q-hp5q-x767",
  "modified" : "2025-11-13T22:46:24Z",
  "published" : "2025-11-13T22:46:24Z",
  "aliases" : [ "CVE-2025-64525" ],
  "summary" : "Astro vulnerable to URL manipulation via headers, leading to middleware and CVE-2025-61925 bypass",
  "details" : "## Summary\n\nIn impacted versions of Astro using [on-demand rendering](https://docs.astro.build/en/guides/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:\n\n- Middleware-based protected route bypass (only via `x-forwarded-proto`)\n- DoS via cache poisoning (if a CDN is present)\n- SSRF (only via `x-forwarded-proto`)\n- URL pollution (potential SXSS, if a CDN is present) \n- WAF bypass\n\n## Details\n\nThe `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: \n\n[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#L97)\n[https://github.com/withastro/astro/blob/970ac0f51172e1e6bff4440516a851e725ac3097/packages/astro/src/core/app/node.ts#L121](https://github.com/withastro/astro/blob/970ac0f51172e1e6bff4440516a851e725ac3097/packages/astro/src/core/app/node.ts#L121)\n\nThese header values are then used directly to construct URLs.\n\nBy 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.\n\nIf the following header value is injected when requesting the path `/ssr`:\n\n```\nx-forwarded-proto: https://www.malicious-url.com/?tank=\n```\n\nThe complete URL that will be created is: `https://www.malicious-url.com/?tank=://localhost/ssr`\n\nAs a reminder, URLs are created like this:\n\n```\nurl = new URL(`${protocol}://${hostnamePort}${req.url}`);\n```\n\nThe 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}`.\n\nThis 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.\n\nThe same logic applies to `x-forwarded-port`, with a few differences.\n\n> [!NOTE]\n> The `createRequest` function is called every time a non-static page is requested. Therefore, all non-static pages are exploitable for reproducing the attack.\n\n## PoC\n\nThe PoC will be tested with a minimal repository:\n\n- Latest Astro version at the time (`2.16.0`)\n- The Node adapter\n- Two simple pages, one SSR (`/ssr`), the other simulating an admin page (`/admin`) protected by a middleware\n- A middleware example copied and pasted from the official Astro documentation to protect the admin page based on the path\n\n[Download the PoC repository](https://github.com/zhero-web-sec/astro-app)\n\n### Middleware-based protected route bypass - x-forwarded-proto only\n\nThe middleware has been configured to protect the `/admin` route based on [the official documentation](https://docs.astro.build/en/guides/authentication/):\n\n```ts\n// src/middleware.ts\nimport { defineMiddleware } from \"astro/middleware\";\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n  const isAuthed = false; // auth logic\n  if (context.url.pathname === \"/admin\" && !isAuthed) {\n    return context.redirect(\"/\");\n  }\n  return next();\n});\n```\n\n1. When tryint to access `/admin` the attacker is naturally redirected :\n   ```sh\n   curl -i http://localhost:4321/admin\n   ```\n   <img width=\"620\" height=\"102\" alt=\"image\" src=\"https://github.com/user-attachments/assets/15a7bffc-ee56-4ed9-84b2-091cf4d78351\" />\n\n2. The attackr can bypass the middleware path check using a malicious header value:\n   ```sh\n   curl -i -H \"x-forwarded-proto: x:admin?\" http://localhost:4321/admin\n   ```\n   <img width=\"1348\" height=\"159\" alt=\"image\" src=\"https://github.com/user-attachments/assets/d9d9ac1a-5efa-452b-981e-efea8a08d089\" />\n\n#### How ​​is this possible?\n\nHere, with the payload `x:admin?`, the attacker can use the URL API parser to their advantage:\n\n- `x:` is considered the protocol\n- 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`\n\nDuring 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.\n\n### SSRF\n\nAs 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).\n\nExample of code reusing the \"origin\" URL, concatenating it to the API endpoint :\n\n<img width=\"601\" height=\"418\" alt=\"image\" src=\"https://github.com/user-attachments/assets/9c374b2c-841c-48d6-98f1-3b3f5b060802\" />\n\n### DoS via cache poisoning\n\nIf 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.\n\nA `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`\n<img width=\"998\" height=\"108\" alt=\"image\" src=\"https://github.com/user-attachments/assets/4bab58e5-3045-4e25-9aa2-2f72a0832d86\" />\n\nSame logic applies to `x-forwarded-port` : `curl -i -H \"x-forwarded-port: /vulnerable?\" http://localhost:4321/ssr`\n\n#### How ​​is this possible?\n\nThe 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`.\n\n### URL pollution\n\nThe exploitability of the following is also contingent on the presence of a CDN, and is therefore cache poisoning.\n\nIf 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:\n\n```\nx-forwarded-proto: javascript:alert(document.cookie)//\n```\n\nresults in the following URL object:\n\n<img width=\"444\" height=\"202\" alt=\"image\" src=\"https://github.com/user-attachments/assets/c2990626-da5b-4868-9093-dbb9b34780ba\" />\n\nIt is also possible to inject any link, always, if the value of `request.url` is used on the server side to create links.\n\n```\nx-forwarded-proto: https://www.malicious-site.com/bad?\n```\n\n**The attacker is more limited with `x-forwarded-port`**\n\nIf 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:\n\n```\nX-Forwarded-Port: /nope?\n```\n\nExample of an Astro website: \n<img width=\"1627\" height=\"298\" alt=\"Capture d’écran 2025-11-03 à 22 07 14\" src=\"https://github.com/user-attachments/assets/02de5e67-f48d-4bf4-810d-6b0714ad2c12\" />\n\n### WAF bypass\n\nFor 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:\n\n[https://zhero-web-sec.github.io/research-and-things/react-router-and-the-remixed-path](https://zhero-web-sec.github.io/research-and-things/react-router-and-the-remixed-path)\n\nNote: A section addressing DoS attacks via cache poisoning using the same vector was also included there.\n\n### CVE-2025-61925 complete bypass\n\nIt is possible to completely bypass the vulnerability patch related to the `X-Forwarded-Host` header.\n\nBy sending `x-forwarded-host` with an empty value, the `forwardedHostname` variable is assigned an empty string. Then, during [the subsequent check](https://github.com/withastro/astro/blob/7a5f28006e9b1f6ad77c7884991ba551ca9ff35b/packages/astro/src/core/app/node.ts#L107), the condition fails because `forwardedHostname ` returns `false`, its value being an empty string:\n\n```\nif (forwardedHostname && !App.validateForwardedHost(...))\n\n```\n\nConsequently, 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](https://url.spec.whatwg.org/#scheme-state), requiring an `authority state`.\n\nFrom there, the following request on the example SSR application (astro repo) yields an SSRF:\n<img width=\"1878\" height=\"456\" alt=\"Capture d’écran 2025-11-06 à 21 18 26\" src=\"https://github.com/user-attachments/assets/c5cca89c-9c65-46f6-bf70-cd7a90a9e0d9\" />\n*empty `x-forwarded-host` + the target `host` in the path*\n\n## Credits\n\n- Allam Rachid ([zhero;](https://zhero-web-sec.github.io/research-and-things/))\n- Allam Yasser (inzo)",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "astro"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "2.16.0"
      }, {
        "fixed" : "5.15.5"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/withastro/astro/security/advisories/GHSA-hr2q-hp5q-x767"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2025-64525"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/withastro/astro/commit/dafbb1ba29912099c4faff1440033edc768af8b4"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/withastro/astro"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/withastro/astro/blob/970ac0f51172e1e6bff4440516a851e725ac3097/packages/astro/src/core/app/node.ts#L121"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/withastro/astro/blob/970ac0f51172e1e6bff4440516a851e725ac3097/packages/astro/src/core/app/node.ts#L97"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-918" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2025-11-13T22:46:24Z",
    "nvd_published_at" : "2025-11-13T16:15:56Z"
  }
}