How to identify Web Cache Poisoning Vulnerabilities
There are a variety of different types of caches, but most of them have the same main goals – less network traffic, thus shorter response time and less load on the web server. Caches are located between the user and the web page. In a cache, requests for resources and their response, or a copy of the resources (images, texts and other components) are stored for a set time.
The browser cache is located locally on the user’s computer, more precisely in their browser. Thus, another browser on the same computer has no access to the stored contents of the first browser cache. The browser cache belongs to the so-called private caches, as third parties have no access to it.
Proxy servers can also have a cache, but this is not bound to the user; all connections that go through the same proxy server can access it. However, this does not necessarily have to be all visitors to website X.
In addition, the web server can have its own caching mechanisms, for example via a framework such as Drupal, or the used Content Delivery Network (CDN) has its own caching. In some cases, specific products such as Varnish are also used.
There are other types of caches, but the focus of this article is on caching mechanisms that are used by all website visitors, so-called shared caches.
Cache-Control Header is an HTTP header that can be used in requests and responses to control caching. This header determines whether, who and for how long a request or response may be cached. Who means various cache parties such as the browser, a proxy server, CDNs and others.
max-age the maximum cache duration in seconds can be set
Cache-Control: max-age=604800. The cache duration starts from the time when the response was generated by the server.
Requests or responses that contain sensitive content should not be cached for security reasons;
Cache-Conrol: no-store should be used for this.
Further information on the Cache-Control-Header is available in the Mozilla documentation.
So that the cache does not have to compare the complete request with the currently stored request, so-called cache keys are used. Cache keys normally consist of the HTTP query and the host name, but this can also be configured differently. If there is a match, the stored copy of the response is transmitted directly to the request originator. This happens as long as the cached resource is valid.
Possible cache key of this article:
"/en/?labs.20220512" + "www.scip.ch"
Components of the request that are not part of the cache key are called unkeyed inputs. Unkeyed inputs can be request headers, cookies, in rare cases also parts of the query string or parts of the request body (Fat GET requests). These unkeyed inputs can lead to problems or enable a web cache poisoning vulnerability.
In the following example, the
X-Forwarded-Host header is used to generate an Open Graph URL within a meta tag. In this case, changing the X-Forwarded-Host header could determine the domain and load a different image. Since this is cached, all other visitors with the same query string within the validity period of the cache will also receive this image.
GET /en?param=test HTTP/1.1 Host: www.example.com X-Forwarded-Host: evil HTTP/1.1 200 OK ... <meta property="og:image" content="https://evil/images/cat.png" />
GET /en?param=test2 HTTP/1.1 Host: www.example.com X-Forwarded-Host: a."><script>alert(1)</script> HTTP/1.1 200 OK ... <meta property="og:image" content="https://a."><script>alert(1)</script>" />
The examples are inspired by the paper Practical Web Cache Poisoning, which describes many different types of web cache poisoning attacks. Web cache poisoning payloads can also include open redirects or DOM-XSS attacks, as well as other attacks.
First, start by looking for unkeyed inputs and what their effect is, i.e. how they are returned in the server response. This evaluation can either be done manually with known request headers and other frequently affected unkeyed inputs or with the help of Param Miner, an open source burp plugin that greatly simplifies the search for an unkeyed input.
When a promising unkeyed input has been found, the search begins for a payload that is functionally returned by the web server in the corresponding response.
After the payload has been determined, the response with the reflected payload is cached in a final step so that it is delivered to the desired third-party visitors via the cache used.
When testing an application for a web cache poisoning vulnerability, a cache buster should be used. This is a random URL parameter that prevents normal users from being affected by testing.
The simplest and at the same time most drastic countermeasure is to do without caching completely. This works for small websites that have little network traffic and a web server with sufficient performance. In this case, it may be that web frameworks are used that have caching by default, but since it is not needed, it can be deactivated and thus the attack surface can be reduced.
If caching cannot be dispensed with completely, only static resources should be cached. This prevents web cache poisoning, since no user input is processed when requests are made to static resources, they are also not susceptible to web cache poisoning attacks.
If the caching of static content is not sufficient, it can be extended to dynamic content, but should not process input from request headers, cookies or reflect unkeyed inputs in the response. It should be noted that some frameworks have their own request headers, which could also be vulnerable. The possibility of web cache poisoning due to a configuration error cannot be ruled out.
There is also the option of including unkeyed request headers in the cache key, which makes the cache key longer. Because the cache key now contains additional components, there may occasionally be several entries in the cache, but the possibility of a successful web cache poisoning attack is reduced. The contents of request headers should not be used if they are not part of the cache key. In addition, HTTP headers should never be returned directly to users for cached resources.
GET requests with body (Fat GET), which result in changes to the server response, should be avoided.
An additional security measure would be to test the web application by using the Burp plugin Param Miner and to check detected HTTP headers or other unkeyed inputs more closely.
Web cache poisoning was long considered a theoretical attack, but Bug Bounty platforms have shown that this combination of a cache attack and another vulnerability is now also quite practical. Therefore, cache configurations should be well thought out and not left at the default configuration. Cache mechanisms supplied with the framework should be deactivated if there is no need for caching. If you would like to find a web cache poisoning vulnerability yourself in a training environment, we recommend Portswigger Labs.
Our experts will get in contact with you!
Our experts will get in contact with you!
Further articles available here