Web Cache Poisoning

Web Cache Poisoning

Resources:

Practical Web Cache Poisoning

Practical Web Cache Poisoning: Redefining 'Unexploitable'

Cache:

Caching is intended to speed up page loads by reducing latency, and also reduce load on the application server.

Some companies host their own cache using software like:

  • Varnish

  • Cloudflare(caches scattered across geographical locations.)

  • Drupal have a built-in cache.

How Cache Works:

  1. Request is being received by Cache

  2. Cache Checks whether it has a copy of this exact resource being requested then it response with it

  3. If not ,Cache forwards the request to the application server.

Methods used by cache to Identify whether two requests are trying to load the same resource:

  • byte-for-byte compare.(ineffective)

📌 Caches tackle this problem using the concept of cache keys

Typical cache keys:

  • URL and it’s Get parameters

  • Host header

Based on those 2 Keys Cache Will handle The next 2 Requests in the same way

GET /blog/post.php?mobile=1 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 … Firefox/57.0
Cookie: language=en;
GET /blog/post.php?mobile=1 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 … Firefox/57.0
Cookie: language=pl;

📌 As a result, the page will be served in the wrong language to the second visitor.

Solution:

Vary response header is Used to specify additional request headers that should be keyed.But it’s used in a rudimentary way.

Cache Poisoning:

📌 is to send a request that causes a harmful response that gets saved in the cache and served to other users.

Methodology :

  1. Identify unkeyed inputs(Manually Or Using Param miner or any other fuzzing tool)

    📌 Cached responses can mask unkeyed inputs - The response is the same thus,you can’t notice the unkeyed inputs.Changing it while testing won’t be effective(it’s already cached) Therefore,using a cache-buster would help If you test manually. or you can ensure every request has a unique cache key by adding a parameter with a value of $randomplz to the query string or in your fuzzing tool.

  2. Identify how much damage you can do with it.

  3. Try and get it stored in the cache

📌 To keep it legal,Cache The request using a get parameter with a fixed value just to affect your requests while testing.

Basic Poisoning:

Request Header That Found to be reflected:
/en?dontpoisoneveryone=1 HTTP/1.1
Host: www.redhat.com
X-Forwarded-Host: a."><script>alert(1)</script>
Respose:
<meta property="og:image" content="https://a."><script>alert(1)</script>"/>

In most cases this is just a self-xss

dontpoisoneveryone=1 is our cache-buster in this case.

Performing the request to the same endpoint Would case an alert

Request:
GET /en?dontpoisoneveryone=1 HTTP/1.1
Host: www.redhat.com

HTTP/1.1 200 OK

Response:
<meta property="og:image" content="https://a."><script>alert(1)</script>"/>

Discreet poisoning:

After Performing the Basic Poisoning on /en?dontpoisoneveryone=1,We would like to perform it on the homepage now.Which would be normally cached already,Therefore you need to wait to cache Your malicious request when the last cached request is over.

The response headers 'Age' and 'max-age' respectively specify the age of the current response, and the age at which it will expire.the response expires when the age == max-age.

Sometimes you notice the max-age is missing but at some point the server will reset the age.

Find when by automating requests in different times and observing the response.

Age: 174
Cache-Control: public, max-age=180

Or you can just spam your malicious request using burp intruder.

Selective Poisoning:

Vary: User-Agent

As seen in the response,The User-Agent is being used as a key for cache.

So you might poison it with the most used user agents.

If you are targeting someone(selective) you can just poison the cache with his user agent

anyone with that user agent will be affected.

DOM Poisoning:

Sometimes it’s not about just an XSS payload,it’s complicated somehow,

an example :

The site used to request a json data which is used to translete something into 
another,example:
site.com/api/i18n/es
{"Show more":"Mostrar más"}
<< Those info was reached after testing the function using burp collaborator >>
but it in the X-Forward-Host Header and you get the request.
Note:The header gets reflect in <body data-site-root="Header Value">
So you can just use your own translation file which contains something like:
{"Show more":"<script>alert("Hello There")</script>"}
Therefore any page contains "Show more" would be translated into this xss payload

Route Poisoning:

Some applications use headers for internal request routing

📌 HubSpot is giving the X-Forwarded-Server header priority over the Host header

Go to HubSpot,Create a web page, paste its URL into the X-Forwarded-Server,

Your response would be your web page and should be cached and served to all users,it’s sound like an subdomain take over.

Hidden Route Poisoning:

📌 Cloudflare's blog is hosted by Ghost, who are clearly doing something with the X-Forwarded-Host header.

Example Found on [1][”Hidden Route Poisoning:”]

Chaining Unkeyed Inputs:

We might chain 2 headers

A good combination of

X-Forward Headers:
X-Forwarded-Scheme: nothttps
X-Forwarded-Protocol: https
X-Forwarded-Ssl: on
X-Url-Scheme: https
X-Forwarded-Host:attacker.com

Open Graph Hijacking:

X-Forward-Host:attacker.com
ـــــــــــــــــــــــــــــــــ
<meta property="og:url" content='https://attacker.com/en'/>

Open Graph is a protocol created by Facebook to let website owners dictate what happens when their content is shared on social media. The og:url parameter we've hijacked here effectively overrides the URL that gets shared, so anyone who shares the poisoned page actually ends up sharing content of our choice.

📌 the application sets 'Cache-Control: private', and Cloudflare refuse to cache such responses. Fortunately, other pages on the site explicitly enable caching.📌 The 'CF-Cache-Status' header is an indicator that Cloudflare is considering caching this response.📌 Cloudflare have even more regional caches📌 I suspect that quite a few websites start using a service like Cloudflare for DDoS protection or easy SSL, and end up vulnerable to cache poisoning simply because caching is enabled by default.

Local Route Poisoning:

📌 X-Original-URL and X-Rewrite-URL which override the request's path

📌 Which might be used to convert reflected XSS into stored XSS some times.Something Like that:(**Internal Cache Poisoning)**

Open Redirect + Cache Poisoning:

GET /?destination=https://evil.net\@business.pinterest.com/ HTTP/1.1
Host: business.pinterest.com
X-Original-URL: /foo.js?v=1

Leads to :

GET /foo.js?v=1 HTTP/1.1

HTTP/1.1 302 Found
Location: https://evil.net\@unity.com/

Nested cache poisoning:

📌 If the site uses an external cache.we can use the internal cache to poison the external cache

Web Cache Poisoning via Fat GET Request:

This web application is using a caching system. By sending a GET request with a request body (a "fat" GET request) it was possible to force the caching system to cache a response that contains user-controlled input.

Notes:


You can identify the cache keys using :

Pragma: x-get-cache-key
also some custom headers based on the CDN - Akamai implements it's own headers

Always poison the javascript entries trying to get a redirect to your exploit.


Some UTM analytics parameters are unkeyed like :

utm_source
utm_content
...

if you use a semicolon (;) to append another parameter to utm_content,
the cache treats this as a single parameter.
This means that the extra parameter is also excluded from the cache key.
aka Parameter cloaking

Example OF The Last 2 Notes:

GET /js/geolocate.js?callback=setCountryCookie&utm_content=;callback=alert(1)
%3bsetCountryCookie
alert(1);setCountryCookie({"country":"United Kingdom"});

XSS + Cache Poisoning:

Some Useful headers: or Just Use Param Miner.

X-Forwarded-Host
X-Host
X-Forwarded-Server
X-HTTP-Host-Override
Forwarded
-----------------------
X-Original-URL 
X-Rewrite-URL 

  • Activate “Add fcbz cachebuster”.while testing your payloads that you want it to be cached

  • Activate “Add Dynamic cachebuster” while guessing the used inputs.



  • Ibrahim Radi

Ibrahim Radi - Bug Bounty Hunter - HackerOne | LinkedIn

Last updated