Web Caching Back

Security related information and use within the scope of pentesting


Description & Methodology

A web cache sits between the client (browser) and the requested resource (e.g. an image, HTML document or a JSON response) mostly in form of a CDN (Content Delivery Network; like Cloudflare) or a hosted solution (like Vanish) and serves a cached version of the requested content to increase performance. Cache keys are being used to identify whenever a resource was already cached for a specific request or not. These Cache keys mostly consists of the Route and the Host header (additionally a Vary header can be used to to specify other headers for keying) [1].


diagram


1. Identification

To identify if a web cache is in use it's often enough to look into the header section of the HTTP response. Several headers like Cache-Control, Age, X-Served-By or Via: varnish [..] are informing the client if a cache is present and when it does expire (Age). Otherwise a DNS lookup might be help, identifying a cache by a CNAME record pointing to e.g. *.edgekey.net.


2. Unkeyed Input

Unkeyed input are input values (like a HTTP header) which might affecting the HTTP response (status code, response body or behavior) but aren't part of the cache key. Therefore overwriting the cache with variable unkeyed input 1. updates the cache and 2. the behavior for secondary visitors based on the influence of the input values. This process is called Cache Poisoning. Here are few examples of unkeyed input values in form of header values:

Values: User-Agent
Values: Cookie
Header: X-Forwarded-Host
Header: X-Host
Header: X-Forwarded-Server 
Header: X-Forwarded-Scheme (header; also in combination with X-Forwarded-Host)
Header: X-Original-URL (Symfony) 
Header: X-Rewrite-URL (Symfony)

You can quickly identify unkeyed input by probing with these headers as part of a HTTP request. If the HTTP response of the cache contains or react to your payload (input value) you successfully injected unkeyed input into the cache. Use a cache buster by adding a unique value as pseudo-parameter to the request so that only you can access the modified cache. Example:

GET /test?buster=123 HTTP/1.1
Host: target.com
X-Forwarded-Host: attacker.com

3. Cache Poisoning

I will describe an example of Basic Poisoning . The X-Forwarded-Host header is our unkeyed input. After submitting it as part of the HTTP request we see that it's being reflected back by the application unfiltered. This allows us to XSS all secondary visitors being presented with a cached version including our payload, or turning the scenario of a self XSS into a persistent XSS.

GET /test?buster=123 HTTP/1.1
Host: target.com
X-Forwarded-Host: test"><script>alert(1)</script>

HTTP/1.1 200 OK
Cache-Control: public, no-cache
[..]
<meta property="og:image" content="https://test"><script>alert(1)</script>"> 

It's important to know the caching behavior of a cache server when trying to exploit cache poisoning vulnerabilities. Knowing the time a cache refreshes is important for placing Discrete Poisoning. These values can be guessed (via bruteforce; dirty) or computed e.g. via the Age and max-age value of Cache-Control headers.


A cache can vary for a specific header like the User-Agent (therefore the declared header is part of the cache-key) . Using Selective Poisoning a poisoned cache might only be working for a specific browser.
Here is a quick illustration:

GET /test?buster=123 HTTP/1.1
Host: target.com
User-Agent: Mozilla/5.0 … Firefox/60.0
X-Forwarded-Host: test"><script>alert(1)</script>

HTTP/1.1 200 OK
X-Served-By: cache-lhr6335-LHR
Vary: User-Agent, Accept-Encoding
[..]

This behavior could be bypassed by using a list of common user-agents and repeating the HTTP request for each entry.


Example 1: "Cache poisoning using NULL bytes and long URLs" [2]

The researcher identified that it was possible to use web-cache poisoning on *.greenhouse.io/embed/job_board/js?for= {IP} in combination with an extreme long arbitrary string of nullbytes (%00; unkeyed input) to produce a denial of service (DOS) due a HTTP error 414: Request URL Too Long for all customers requesting that endpoint.


Sources

All credits (unless marked) are going out to https://portswigger.net/blog/practical-web-cache-poisoning an article written by James Kettle (twitter.com/albinowax) which describes his brilliant research on this topic (the articles is more complex and provides additional/deeper information about this topic; be sure to check it out).