# Astro framework and standards weaponization

Astro's Node adapter trusts x-forwarded-proto/port headers, letting attackers rewrite the request URL to bypass path middleware, trigger SSRF, and poison caches with stored XSS.

- Payout: Research
- Severity: high
- Program: Astro
- Disclosed: 2025-11-05
- CVE: CVE-2025-64525
- Write-up quality: 8/10
- Original: https://zhero-web-sec.github.io/research-and-things/astro-framework-and-standards-weaponization
- Library page: https://bugbountylibrary.com/r/blog%3Azhero-web-sec-github-io-research-and-things-astro-framework-and-standards-weaponization
- Tags: area:web domain:web impact:ato kind:research platform:research sev:high tech:nodejs technique:chained technique:parser-differential vuln:access-control vuln:auth-bypass vuln:cache-deception vuln:ssrf vuln:xss

**Target:** Astro Node adapter createRequest / SSR URL construction
**Root cause:** The Node adapter builds the request URL via `new URL(`${protocol}://${hostnamePort}${req.url}`)` using unvalidated x-forwarded-proto and x-forwarded-port headers, letting an attacker control the scheme/host/path of the WHATWG URL instance.
**Steps:**
1. Send request to an SSR route with header `x-forwarded-proto: x:admin?` so the URL becomes `x:admin?://host/path`, placing pathname as `admin` (no leading slash).
2. Middleware checks `context.url.pathname === "/dashboard"` and fails since `admin` != `/admin`, but router's prependForwardSlash turns it into `/admin` and matches the protected route.
3. For SSRF: set `x-forwarded-proto: https://www.malicious-url.com/nope?tank=` so `${origin}` used in server-side fetch points to attacker host.
4. For SXSS: set `x-forwarded-proto: javascript:/links#/;alert('Long live Algeria')//` so Astro.url-based links render a javascript: URL; cache via CDN using `GET /_astro/page.zhero.js?cache-buster=1` with the header.
5. To bypass CVE-2025-61925 patch: send empty `x-forwarded-host` so forwardedHostname is falsy and whitelist check is skipped; parser then takes host from the path, e.g. `http://www.attacker-host.net/`.
**How found:** After prior research on react-router/remix x-forwarded-host, the researchers inspected Astro's createRequest URL template and noticed x-forwarded-proto and x-forwarded-port were still unvalidated. They studied the WHATWG URL state machine to understand how a non-special scheme like `x:` parses `x:admin?` as a path without authority, then combined that with prependForwardSlash to defeat middleware path checks.
**Exploitation:** Attacker controls the URL Astro builds server-side. Path-based middleware auth checks compare against a slashless pathname while the router prepends a slash, granting access to protected routes. When the app reuses the request origin for outbound fetches, the attacker redirects them to arbitrary hosts (SSRF). When Astro.url is used to build links, a javascript: scheme payload yields stored XSS, cacheable via CDN cache-deception on a fake static asset path. Empty x-forwarded-host bypasses the CVE-2025-61925 whitelist entirely.
**Escalation:** Chained URL rewrite with CDN cache-deception to convert reflected SXSS into stored XSS served to all users; empty x-forwarded-host bypasses the prior CVE patch.
**Insight:** When a framework builds URLs from forwarded headers, study the WHATWG parser's special-vs-non-special scheme handling: a non-special scheme like `x:admin?` yields a slashless pathname that middleware compares literally, while the router's prependForwardSlash normalizes it — a parser-differential auth bypass. Also test empty-string values against whitelist checks (`if (host && !validate(host))`).

## Proof of concept (copied exactly from the original report)

**x-forwarded-proto payload bypassing middleware path check**

```
x-forwarded-proto: x:admin?
```

**javascript: payload for stored XSS via Astro.url**

```
x-forwarded-proto: javascript:/links#/;alert('Long live Algeria')//
```

