Abusing HTTP Misconfigurations  

Introduction to Web Cache Poisoning

Web cache poisoning is an advanced attack vector that forces a web cache to serve malicious content to unsuspecting users visiting the vulnerable site. Web caches are often used in the deployment of web applications for performance reasons as they reduce the load on the web server. By exploiting web cache poisoning, an attacker may be able to deliver malicious content to a vast number of users. Web cache poisoning can also be used to increase the severity of vulnerabilities in the web application. For instance, it can be used to deliver reflected XSS payloads to all users that visit the website, thus eliminating the need for user interaction which is usually required for exploiting reflected XSS vulnerabilities.

Commonly used web caches include Apache, Nginx, and Squid.


Inner Workings of Web Caches

Benefits of Caching

When providing a web service that is used by a large number of users, scalability is important. The more users use the service, the more load is generated on the web server. To reduce the web server load and distribute users between redundant web servers, Content Delivery Networks (CDNs) and reverse proxies can be used.

Web caches are a part of this performance-enhancing infrastructure. They are located between the client and the server and serve content from their local storage instead of requesting it from the web server. If a client requests a resource that is not stored in the web cache, the resource will be requested from the web server. The web cache then stores the resource locally and is thus able to respond to any future requests for that resource without requesting it from the web server. Web caches typically store resources for a limited time window to allow changes to propagate once the cache has been refreshed.

How do Web Caches work?

As discussed above, web caches store resources to reduce the load on the web server. These can be static resources such as stylesheets or script files, but also dynamic responses generated by the web server based on user-supplied data such as search queries. To serve cached resources, the web cache needs to be able to distinguish between requests to determine whether two requests can be served the same cached response, or if a fresh response needs to be fetched from the web server.

Just comparing requests byte-by-byte to determine whether they should be served the same response is highly inefficient, as different browsers send different headers that do not directly influence the response, for instance, the User-Agent header. Furthermore, web browsers commonly populate the Referer header to inform the web server from where a resource has been requested, however, in most cases, this does also not directly influence the response.

To circumvent these issues, web caches only use a subset of all request parameters to determine whether two requests should be served the same response. This subset is called Cache Key. In most default configurations, this includes the request path, the GET parameters, and the Host header. However, cache keys can be configured individually to include or exclude any HTTP parameters or headers, to work optimally for a specific web application.

Let's have a look at an example. Assuming the web cache is configured to use the default configuration of request path, GET parameters, and host header as the cache key, the following two requests would be served the same response.

GET /index.html?language=en HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.125 Safari/537.36
Accept: text/html

The second request has the same cache key and thus gets served the same response. The requests differ in the User-Agent header due to different operating systems and contain a different Accept header as well.

GET /index.html?language=en HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 13_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml

However, if a third user requests the same page in a different language via the GET parameter language, the cache key is different (since the GET parameters are part of the cache key), thus the web cache serves a different response. This is obviously intended behavior, as otherwise all users would be served the same cached response in the same language, rendering the language-parameter useless:

GET /index.html?language=de HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 13_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15
Accept: text/html

We distinguish between keyed parameters and unkeyed parameters. All parameters that are part of the cache key are called keyed, while all other parameters are unkeyed. For instance, in the above example, the User-Agent and Accept headers are unkeyed.


Web Cache Configuration

To conclude this section, let's have a look at a sample Nginx config file that configures a simple web cache:

http {
  proxy_cache_path /cache levels=1:2 keys_zone=STATIC:10m inactive=24h max_size=1g;

  server {
    listen       80;

    location / {
      proxy_pass             http://172.17.0.1:80;
      proxy_buffering        on;
      proxy_cache            STATIC;
      proxy_cache_valid      2m;
      proxy_cache_key $scheme$proxy_host$uri$args;
      add_header X-Cache-Status $upstream_cache_status;
    }
  }
}

Here is a short explanation of the parameters, for a more detailed overview have a look at the Nginx documentation here:

  • proxy_cache_path sets general parameters of the cache like the storage location
  • proxy_pass sets the location of the web server
  • proxy_buffering enables caching
  • proxy_cache sets the name of the cache (as defined in proxy_cache_path)
  • proxy_cache_valid sets the time after which the cache expires
  • proxy_cache_key defines the cache key
  • add_header adds the X-Cache-Status header to responses to indicate whether the response was cached

Now if we request the same resource twice, we can see that the first response is not cached, while the second response is served from cache, as noted by the X-Cache-Status header in the response:

image

image

The cache key can be configured to only include certain GET parameters, by modifying the configuration like so:

<SNIP>
proxy_cache_key $scheme$proxy_host$uri$arg_language;
<SNIP>

With this configuration, only the language parameter is keyed, while all other GET parameters are unkeyed. This results in two requests to /index.html?language=de&timestamp=1 and /index.html?language=de&timestamp=2 being served the same response from cache. This makes sense if not all parameters influence the response itself, like a timestamp for instance.

Previous

+10 Streak pts

Next