# Unkeyed header cache poisoning

Headers that change the response but are not in the cache key, poisoning shared caches

_Area: Web infrastructure and server-side requests. Seen in 16 disclosed reports; 6 confirmed bounties, median $1,750, range $750-$3,000; latest 2024-06-24. Updated 2026-10-23._

## How it works
A cache (CDN or reverse proxy) stores a response under a key built from some request attributes, usually path plus a few headers. The origin builds the body, status, or redirect from other request headers it trusts. When a header changes the response but is not part of the key, one request stores a response every later visitor receives. The broken assumption: "the cache key covers everything that changes the response." It does not, so an attacker-supplied header becomes a persistent shared value.

## Where it shows up
- **Reflection into output:** origin reflects `X-Forwarded-Host`, `X-Forwarded-Proto`, `X-Forwarded-Port`, or `Host` into HTML attributes, canonical/asset URLs, or redirect `Location` [E1](https://bugbountylibrary.com/r/h1%3A303730.md)[E2](https://bugbountylibrary.com/r/blog%3Asamcurry-net-universal-xss-on-netlifys-next-js-library.md)[E5](https://bugbountylibrary.com/r/h1%3A409370.md).
- **Status override:** a header sets the response status or body directly, e.g. framework-internal `x-invoke-status`/`x-middleware-*` read from client requests [E4](https://bugbountylibrary.com/r/blog%3Azhero-web-sec-github-io-research-and-things-nextjs-and-cache-poisoning-a-quest-for-the-black-hole.md), or origin-specific `x-http-method-override` producing an empty HEAD body [E3](https://bugbountylibrary.com/r/h1%3A1160407.md).
- **Proxy/optimizer routes:** image optimizers or fetch handlers interpolate headers into an outbound URL [E2](https://bugbountylibrary.com/r/blog%3Asamcurry-net-universal-xss-on-netlifys-next-js-library.md).
- **Cache-key misconfig (broader class):** key excludes query string, path, or most headers, and error responses (400/403/404) are cacheable [E6](https://bugbountylibrary.com/r/blog%3Agithub-com-ankitcuriosity-write-ups-blob-main-web-20cache-20poisoning-20-20capability-20to-20disable-e2-88-95deface-20th.md)[E7](https://bugbountylibrary.com/r/h1%3A1173153.md)[E8](https://bugbountylibrary.com/r/h1%3A1219038.md).

## How to review
For each header the origin reads, ask: does it change the response, and is it in the cache key? The invariant: every request attribute affecting status, headers, or body must be keyed, or stripped/validated before use. Grep for `req.headers[...]` feeding `res.statusCode`, `Location`, URLs, or template output. Read the CDN cache-key config concretely: which headers, query string, and path are keyed; whether `Vary` is honored; whether error responses are cacheable. Use a unique cache-buster query param so you poison only your own key, then replay without the trigger header to confirm the cached response is served to clean requests [E6](https://bugbountylibrary.com/r/blog%3Agithub-com-ankitcuriosity-write-ups-blob-main-web-20cache-20poisoning-20-20capability-20to-20disable-e2-88-95deface-20th.md). Check sibling forwarding headers after any single-header fix [E1](https://bugbountylibrary.com/r/h1%3A303730.md)[E5](https://bugbountylibrary.com/r/h1%3A409370.md).

## Variants seen
- Reflected host into client-side fetch URL, yielding stored DOMXSS [E1](https://bugbountylibrary.com/r/h1%3A303730.md).
- Proto header rewriting fetch URL, caching malicious SVG for XSS/SSRF [E2](https://bugbountylibrary.com/r/blog%3Asamcurry-net-universal-xss-on-netlifys-next-js-library.md).
- Internal status header forcing cacheable 200 error page [E4](https://bugbountylibrary.com/r/blog%3Azhero-web-sec-github-io-research-and-things-nextjs-and-cache-poisoning-a-quest-for-the-black-hole.md).
- Method-override header caching empty HEAD body as site-wide DoS [E3](https://bugbountylibrary.com/r/h1%3A1160407.md).
- Port header reflected into cached 302 redirect to invalid port [E5](https://bugbountylibrary.com/r/h1%3A409370.md).
- Unkeyed custom header reflected into cached 400, with path/query also unkeyed [E6](https://bugbountylibrary.com/r/blog%3Agithub-com-ankitcuriosity-write-ups-blob-main-web-20cache-20poisoning-20-20capability-20to-20disable-e2-88-95deface-20th.md).
- Malformed Authorization triggering cached Azure 403 [E7](https://bugbountylibrary.com/r/h1%3A1173153.md).
- Obscure `trailer` header producing cached 400 [E8](https://bugbountylibrary.com/r/h1%3A1219038.md).

## Why fixes fail
Blocking one header leaves siblings exploitable; forwarding headers remain reflected after host-header awareness [E1](https://bugbountylibrary.com/r/h1%3A303730.md)[E5](https://bugbountylibrary.com/r/h1%3A409370.md).

## Impact and severity
Stored XSS, SSRF, defacement, and persistent DoS affecting all users. Worse when responses are broadly cached and error pages are cacheable [E6](https://bugbountylibrary.com/r/blog%3Agithub-com-ankitcuriosity-write-ups-blob-main-web-20cache-20poisoning-20-20capability-20to-20disable-e2-88-95deface-20th.md)[E7](https://bugbountylibrary.com/r/h1%3A1173153.md)[E8](https://bugbountylibrary.com/r/h1%3A1219038.md).

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A303730.md
- E2: https://bugbountylibrary.com/r/blog%3Asamcurry-net-universal-xss-on-netlifys-next-js-library.md
- E3: https://bugbountylibrary.com/r/h1%3A1160407.md
- E4: https://bugbountylibrary.com/r/blog%3Azhero-web-sec-github-io-research-and-things-nextjs-and-cache-poisoning-a-quest-for-the-black-hole.md
- E5: https://bugbountylibrary.com/r/h1%3A409370.md
- E6: https://bugbountylibrary.com/r/blog%3Agithub-com-ankitcuriosity-write-ups-blob-main-web-20cache-20poisoning-20-20capability-20to-20disable-e2-88-95deface-20th.md
- E7: https://bugbountylibrary.com/r/h1%3A1173153.md
- E8: https://bugbountylibrary.com/r/h1%3A1219038.md
