Search What is Detectify?
×

Do you trust your cache? – Web Cache Poisoning explained

Carl Ericsson / July 28, 2020

As we are all currently confined to a life at home during the pandemic, it has become more important than ever that our favorite web applications stay fast and reliable. Many modern web applications use web caches to keep up with these demands. While this works wonders from a performance perspective, it also opens up new attack vectors. One of these new attack vectors is called Web Cache Poisoning. 

Web cache poisoning is the act of tricking the web cache to store malicious content that will in turn be served to other users. The three most common ways of poisoning web caches are request smuggling, request splitting and poisoning using unkeyed inputs (also known as practical web cache poisoning). This article will only focus on cache poisoning using unkeyed inputs as it is the most popular way of conducting web cache poisoning at the moment of writing much thanks to the brilliant research done by James Kettle.

How does caching work?

In order to understand what web cache poisoning is and what its consequences are, we need to first take a look at how web caches work. Caching means that you store frequently accessed content in order to speed up subsequent requests to access that content. Some examples of caches include memory caches, DNS caches and web caches. 

A web cache works by storing HTTP responses for a certain amount of time based on a set of rules. The main way a web cache keeps track of what response content to cache, is through something called cache keys. 

Cache keys are parts of a HTTP request that the cache will use to uniquely identify a response. Typically a cache key consists of the values of one or more response headers as well as the whole or part of the URL path. A typical request can look like this:

GET /totally/real/site?isItForReal=true HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0...
Accept: */*
Cookie: language=en;

The parts of the request that are marked in red represent the cache key. If the cache can match the cache key to an existing record in the cache it will respond with that record instead of passing the request along to the origin. This reduces the amount of requests that the origin has to handle, which leads to lower cost for the application owner and often faster response times for users. Web caches can either be implemented locally or through a CDN.

The idea is that cache keys are supposed to reflect any changes to the response. Issues start to happen when parts of the request other than the cache keys can modify the response content. 

Unkeyed and used inputs

It turns out that many applications allow inputs such as query strings, header values and cookie values that aren’t part of the cache key to be reflected in the response. Inputs that aren’t part of the cache key are called unkeyed inputs. Inputs that affect the response are called used inputs. 

The issue becomes clear when you look at these two requests. The application uses a header called X-User-Background to determine what background color the page should have. The problem is that the X-User-Background header is not cached but it is still being used to determine what the response should look like. The unkeyed and used inputs are marked in blue.

First Request
GET /my/vulnerable/page?isVulnerable=true HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: */*
X-User-Background: pink
Cookie: ASP.NET_Session=23131;
Second Request
GET /my/vulnerable/page?isVulnerable=true HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: */*
X-User-Background: blue
Cookie: ASP.NET_Session=31313;

Assuming that the first request gets cached, both requests will receive a page with a pink background even though they have different X-User-Background headers. All because the X-User-Background header was used but not cached. Now in this case it would be more of a nuisance to users than anything else, but what if someone discovered the unkeyed input could be used for more malicious things than changing the background for other people.

Why does this happen?

This often happens when you set a cache layer in front of an application that has previously worked without a cache. If those who implement the web cache are unaware of how the inputs affect the responses of the application, there is a risk that they may leave some used inputs unkeyed.

Furthermore, a lot of different frameworks use request headers to generate responses that most are not aware of. Since these frameworks often abstract away low level logic such as handling request headers, these types of issues often fly under the radar.

An example of Web Cache Poisoning

So how would an attacker abuse this behaviour? This is best illustrated with an example. Let’s say that there is a website that has a header, called X-Who-Made-This, whose value is reflected in the HTML response body. A normal request looks like this:

GET /my/vulnerable/site?background=pink HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
X-Who-Made-this: Imadethis
Accept: html/text
Cookie: ASP.NET_Session=23131;

HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
...
<body>
<div id=”whomadethis”>Imadethis</div>
</body>

As we can see in the response, we are getting a Cache-Control header back, indicating that the application is behind a cache. Furthermore, the X-Who-Made-This header is reflected in the response. If the X-Who-Made-This header value is not properly sanitized it means that the page could be susceptible to a reflected XSS. Even better, if the X-Who-Made-This header value is unkeyed it means that web cache poisoning may be performed. We can confirm this by sending a second request right after the first. If the response is identical to the response of the first request, the endpoint is likely to be vulnerable to web cache poisoning.

GET /my/vulnerable/site?background=pink HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
X-Who-Made-this: Youmadethis
Accept: html/text
Cookie: ASP.NET_Session=23131;

HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
...
<body>
<div id=”whomadethis”>Imadethis</div>
</body>

Once the vulnerability is confirmed an attacker will be able to inject a malicious script tag into the page and have the page stored in the cache. This can be accomplished by sending the request below.

GET /my/vulnerable/site?background=pink HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
X-Who-Made-this: <script>alert(document.domain)</script>
Accept: html/text
Cookie: ASP.NET_Session=23131;

HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
...
<body>
<div id=”whomadethis”>
<script>alert(document.domain)</script>
</div>
</body>

By doing this an attacker will be able to turn a reflected header XSS into a stored XSS that will last until the cache entry expires. Please note that the attacker would most likely have to send the request multiple times over a period of time to ensure that malicious request is the one that gets stored in the cache.

This is just one of multiple ways that request poisoning can be used.

Potential impact

Manipulation of web cache contents means that an attacker could potentially target anyone that tries to access the vulnerable application. It can be used to create a stored XSS, open redirects and Denial-Of-Service depending on what parts of the application are vulnerable.

Here is a video of Detectify co-founder and security researcher, Fredrik N. Almroth explaining web cache poisoning:

How to mitigate it

There are multiple ways to mitigate this type of attack:

  • If possible, make sure to only cache static resources. It might not be possible though since this could mean that more traffic is served to the origin which will likely increase cost. Performance might also suffer because of this.
  • Find all inputs (headers, cookies and query strings) that are reflected in the response without being part of the cache key. Make sure to either disable them, remove them in the cache layer or add them to the cache key.

How to detect it

There are some manual tools that can be used to screen your application for this kind of vulnerability. Some tools like Arjun can be used to find hidden parameters that might be unkeyed inputs. Other tools like the Param Miner can be used to both find hidden and unkeyed inputs, as well as test if they can be used to poison the cache. 

How can Detectify help?

Another way to look for cache poisoning vulnerabilities is to use Detectify. Among other things the Detectify scanner will look for any endpoints that are susceptible to cache poisoning and provide remediation tips and tricks if anything is found. 

Special thanks to:

Travis Isaacson and Kristian Bremberg for content consistency and accuracy.

James Kettle from Portswigger for his brilliant research. He also had an amazing presentation about this at BlackHat.

 

About Detectify

Detectify automates the knowledge of the best ethical hackers in the world to secure websites against 2000+ known vulnerabilities beyond OWASP Top 10. In agile tech, the potential attack surface increases with each release. With Detectify, users monitor subdomains for potential takeovers and remediate security bugs in staging and production as soon as they are known, to stay on top of threatsStart a free 14-day trial today.