Protect against Cache-attacks

I asked my friend (or enemy depending how you look at it) avlidienbrunn if he could name an interesting attack and he said "cache attacks". Indeed, they are really interesting. So let's talk about them and focus on how you can mitigate them.


What is cache?

Cache could be described as a local copy of something, and instead of downloading data whenever you need it, you just use the cache. For example: if you visit example.com in your web browser, all images on that website are saved on your device and the next time you visit example.com, your device will not download the images again - instead it will use the images in your cache.

Cache is everywhere because it's a great feature that greatly increases the performance. In a complete request, many resources are most probably already cached.

How and where can cache be attacked?

The most common cache attack is called "cache poisoning". This type of attack is when an attacker can force a client or server to store malicious cache. This attack is quite serious and it could be described as a form of Man-in-the-middle attack because when the client is talking to the server, it's not the server that's giving the client malicious content by purpose.

Here are some places where cache can be attacked:

  • DNS servers
  • Proxy servers
  • Web browsers
  • Web servers

Cache poisoning - mitigation

There are a few protections against cache poisoning attacks already within a complete request. The best way of protecting cache is to make it easy to authenticate, and that the responder has a safe way of talking to the client to control the cache.

DNS-servers

On the DNS server a method called "The 0x20 hack" can be used to see if a response is exactly the same as the client asked for. If a client want to visit gOOGle.coM, the DNS-server should do a name lookup for exactly gOOGle.coM. If the response would be google.com it should be treated as a forged response - read more about this method in this RFC draft.

Browsers and web servers

Browsers use cache to make less requests. The web server can tell the browser for how long the cache should be stored. Most of this is handled via the
Cache-Control response header. This header can do a lot of things, for example tell the browser that the cache is private and should not be stored by proxy servers, or tell the browser not to make any request at all before the max-time (how long the cache should be stored) is over. Web servers can make a 304 response to tell the client that a resource has not been changed on the server, so the client does not need to refresh the content.

Browsers usually do integrity checks on local cache which will protect if a malware tries to change the cache. However, it's still possible to attack browser cache with a Man-in-the-middle attack. With such attack, an attacker can modify the resources and tell the browser to cache them for a long time. So the attacker does not need to actively be modifying traffic, just once.

A web server should tell clients to correctly handle cache. Best practice is to let non-public content to be sat as private and not allowing proxy-servers to store sensitive response headers (such as Set-Cookie). A good header for a document looks something like:
Cache-Control: private, no-store, must-revalidate, no-cache="set-cookie"

There's a new security header that will soon land in Chrome which tells the browser to clear its cache, local/sessionstorage and/or cookies, this header is called Clear-Site-Data - This header can provide both security and privacy. One example would be to clear all types of browser cache before signing in, and then do the same when signing out. In some cases this could've saved the user against certain types of persistence XSS or miss directions (302's).

It's debatable if cache can provide more security than not having any cache at all in web browsers. Both has its attack surface, but if we include privacy as a factor, I would say no cache is better than using cache.

Proxies

A good reason why we shouldn't let proxy servers store any cache of the document is because an attacker may be able to control it. For instance, if an attacker can change a cookie to visitor=<script>alert(1)</script> and this is executed, the proxy server may cache this "infected" document and send it to all other proxy users. So what we want to do is tell the proxy server not to cache anything that can be controlled by users. Clients can't often change resources such as Javascript, images and stylesheets, so they are safe to cache as long as we trust TOFU (Trust On First Use).

Using cache as a protection

If we posit that you're sitting on a café using public WiFi and an attacker is intercepting your traffic, and that your computer cached scripts on a website before you started to use the public WiFi. In such case must the attacker be able to make your browser to refresh the script so the attacker can change it. This is usually easily done by injecting into responses that the resource have been changed and that the client should download it again, which then will be a modified script by the attacker.

It's however possible to tell the client not to do any requests at all. By using the immutable attribute in the Cache-Control header, the client will not do any requests before the max-time is over, which can be for a whole year. The client will always be able to clear the cache at any moment.
If the attacker can't see any requests, there's nothing to intercept. Unfortunately, the document (the HTML-document) is rarely cached and an attacker will be able to intercept it.

A website can use Appcache to let browsers have persistence access to an origin, even offline. By allowing a website to be accessible offline the attack surface increases, so Appcache will soon be deprecated.

As a security defense a website could do something like:

  • Let user login on somethingrandom.example.com
  • Use Appcache to let the website be accessible offline
  • Now null route (point to 127.0.0.1) somethingrandom.example.com

It's only possible to visit somethingrandom.example.com via the adress bar but you can't do something like
<img src="//somethingrandom.example.com"/> because Chrome will throw a Failed to load resource: net::ERR_CONNECTION_REFUSED

The above method would only be safe if the somethingrandom.example.com origin would be safe. An attacker could persistently infect clients with Appcache if the attacker has access to your web server.


Summary

To conclude, best practice for caching would then be:

  • Non-public content should have the private, no-store & no-cache attribute
  • The immutable attribute should be used on cached resources - No wide support yet
  • Documents should not be cached, either by browsers nor proxy-servers
  • Use Clear-Site-Data when a user signs out
  • (If you don't like Appcache, then change the text/cache-manifest mime-type to something bogus)

If you want me to write about an interesting attack and its mitigation, please tell me via Twitter or just email me at chloe@chloe.re!