# Cache poisoning via reflected unkeyed input

Reflected param or cookie in a cacheable response that cache and origin interpret differently

_Area: Web injection and code execution. Seen in 2 disclosed reports; latest 2024-03-19. Updated 2026-10-24._

## How it works
A response is cached under a key that omits some input the origin actually reflects into the body. The cache stores one user's reflected value and serves it to everyone. The assumption that breaks: that the cache key covers every input that changes the response. A second break: the cache and origin disagree on what the response is (method, framing, or which bytes belong to the body), so an attacker controls bytes of a stored response [E1](https://bugbountylibrary.com/r/blog%3Aportswigger-net-research-trace-desync-attack.md).

## Where it shows up
- Cookies or query params echoed into cacheable responses, especially `.php.js` endpoints [E2](https://bugbountylibrary.com/r/h1%3A1760213.md)
- Backends answering TRACE, which echoes request headers verbatim into the body [E1](https://bugbountylibrary.com/r/blog%3Aportswigger-net-research-trace-desync-attack.md)
- Desync primitives (CL.0, HTTP/2) combined with HEAD/response concatenation [E1](https://bugbountylibrary.com/r/blog%3Aportswigger-net-research-trace-desync-attack.md)
- Template/ESI processors evaluating tags in reflected params, e.g. `<esi:vars>` reading headers [E3](https://bugbountylibrary.com/r/h1%3A1073780.md)
- WAFs that strip one character (quotes) rather than encoding output [E2](https://bugbountylibrary.com/r/h1%3A1760213.md)

## How to review
Check the invariant: every input reflected into a response must either be in the cache key or be encoded so it cannot change structure. Trace each reflected param/cookie to its sink and confirm the response is not cacheable, or that the cache key includes it. For framing, confirm frontend and backend agree on message boundaries and that TRACE/HEAD are disabled or not cached [E1](https://bugbountylibrary.com/r/blog%3Aportswigger-net-research-trace-desync-attack.md). For ESI/template sinks, confirm user input never reaches the processor as markup [E3](https://bugbountylibrary.com/r/h1%3A1073780.md).

## Variants seen
- Cookie reflected into cacheable `.js`, WAF quote-stripping bypassed by splitting tags with quotes so `<`/`>` still form valid tags [E2](https://bugbountylibrary.com/r/h1%3A1760213.md)
- TRACE header reflection plus desync to forge cached responses [E1](https://bugbountylibrary.com/r/blog%3Aportswigger-net-research-trace-desync-attack.md)
- ESI injection leaking HttpOnly cookies, chained with reflected XSS in another param to steal sessions [E3](https://bugbountylibrary.com/r/h1%3A1073780.md)

## Why fixes fail
Quote-stripping WAFs fail because the stripped character can split blocked tokens while `<` and `>` still reconstruct valid tags [E2](https://bugbountylibrary.com/r/h1%3A1760213.md). Blocking one reflection point fails when another param on the same origin is unencoded; here the ESI sink and the XSS sink are separate params, so fixing one leaves the chain open [E3](https://bugbountylibrary.com/r/h1%3A1073780.md).

## Impact and severity
Stored XSS served to all cache users, session/INITIAL_STATE leakage, account takeover [E2](https://bugbountylibrary.com/r/h1%3A1760213.md)[E3](https://bugbountylibrary.com/r/h1%3A1073780.md), and full app compromise via forged cached responses [E1](https://bugbountylibrary.com/r/blog%3Aportswigger-net-research-trace-desync-attack.md). Severity rises with cacheability, HttpOnly-cookie exposure, and same-origin chaining.

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Aportswigger-net-research-trace-desync-attack.md
- E2: https://bugbountylibrary.com/r/h1%3A1760213.md
- E3: https://bugbountylibrary.com/r/h1%3A1073780.md
