# Unlocking Reflected XSS in the Astro framework

Astro Server Islands' `e` param accepts the `file` export (a string path), routing rendering into an unsanitized template branch where the `s` slot JSON is injected raw, yielding reflected XSS.

- Payout: Research
- Severity: high
- Program: Astro
- Write-up by: zhero; & inzo_
- Disclosed: 2025-11-18
- CVE: CVE-2025-64764
- Write-up quality: 7/10
- Original: https://zhero-web-sec.github.io/research-and-things/unlocking-reflected-xss-in-the-astro-framework
- Library page: https://bugbountylibrary.com/r/blog%3Azhero-web-sec-github-io-research-and-things-unlocking-reflected-xss-in-the-astro-framework
- Tags: area:web domain:web impact:ato impact:data-leak kind:research platform:research sev:high tech:nodejs technique:param-pollution technique:source-review vuln:xss

**Target:** Astro Server Islands endpoint /_server-islands/[name]
**Root cause:** When the component export selected via `e` is a string (e.g. the built-in `file` export) rather than a function, Astro falls into a fallback branch that builds its own template and injects the `s` slot values as raw child HTML without sanitization.
**Steps:**
1. Observe a page using a Server Island calls `/_server-islands/ServerTime?e=default&p=<encrypted>&s=`
2. Enumerate exports of the island module (e.g. console.log) revealing `default` (function), `url` (undefined), and `file` (string absolute path)
3. Send `/_server-islands/ServerTime?e=file&p=&s={"zhero;":"<img+src=x+one","inzo":"rror=a","dz":"lert(0)>"}`
4. Server takes the string-typed `file` export, enters the non-function Component branch, and concatenates the `s` values into the generated template as raw child HTML
5. Payload `<img src=x onerror=alert(0)>` executes in the victim's browser
**How found:** Prior research on Astro; noticed the conditional block handling non-function Components builds a template ignoring developer templates, then checked what exports exist on an island module and found `file` is a string, satisfying the branch condition.
**Exploitation:** Attacker crafts a URL to the target's `/_server-islands/[name]` endpoint with `e=file`, empty `p`, and an `s` JSON whose values concatenate into an XSS payload; victim must click the link, then arbitrary JS runs in the app origin enabling data exfiltration or account takeover.
**Escalation:** Splitting the payload across multiple JSON keys in `s` (joined via Object.values().join('')) evades WAF signature matching.
**Insight:** When a framework dispatches on the type of a user-selectable key (here `e` indexing module exports), enumerate all exports — built-in ones like `file`/`url` may have unexpected types that unlock fallback code paths with weaker sanitization than the normal path.

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

**Reflected XSS via server island slot payload**

```
?e=file&p=&s={"zhero;":"<img+src=x+one","inzo":"rror=a","dz":"lert(0)>"}
```

**Component export lookup via e parameter**

```
const componentModule = await imp();
let Component = componentModule[data.componentExport];
```

## Write-ups of the same bug (oldest first)
- 2025-11-18: [Unlocking Reflected XSS in the Astro framework](https://bugbountylibrary.com/r/blog%3Azhero-web-sec-github-io-research-and-things-unlocking-reflected-xss-in-the-astro-framework) — zhero; & inzo_ (this one)
- 2025-11-19: [Astro vulnerable to reflected XSS via the server islands feature](https://bugbountylibrary.com/r/ghsa%3AGHSA-wrwg-2hg8-v723) — cold-try

