June 25, 2026
PortSwigger : DOM XSS in jQuery Selector Sink Using a Hashchange Event
In this lab, the website has a DOM-based XSS vulnerability using a hashchange event.
By danar
1 min read
Lab: DOM XSS in jQuery selector sink using a hashchange event This lab contains a DOM-based cross-site scripting vulnerability on the home page. It uses jQuery's $() selector…
The website uses the value from the URL hash, which is the part after #, and passes it into a jQuery selector. The goal of this lab is to make the victim's browser call the print() function.
SOLUTION
First, I clicked Go to exploit server.
After that, I was redirected to the exploit server page. On this page, I can create an HTML response that will be sent to the victim.
On the exploit server page, I changed the Body section and entered this payload:
<iframe src="https://YOUR-LAB-ID.web-security-academy.net/#" onload="this.src+='<img src=x onerror=print()>'"></iframe><iframe src="https://YOUR-LAB-ID.web-security-academy.net/#" onload="this.src+='<img src=x onerror=print()>'"></iframe>The YOUR-LAB-ID part should be replaced with the lab URL.
After entering the payload, I clicked Store to save it.
Why This Payload Works
The payload uses an iframe to load the vulnerable lab page.
<iframe src="https://YOUR-LAB-ID.web-security-academy.net/#"><iframe src="https://YOUR-LAB-ID.web-security-academy.net/#">This part opens the lab page inside an iframe. The # is important because this lab uses the URL hash as the input source.
Then this part is used:
onload="this.src+='<img src=x onerror=print()>'"onload="this.src+='<img src=x onerror=print()>'"When the iframe finishes loading, the onload event changes the iframe URL by adding this payload into the hash:
<img src=x onerror=print()><img src=x onerror=print()>The <img> tag tries to load an image from src=x. Because x is not a valid image source, the image fails to load.
When the image fails to load, the onerror event runs:
print()print()This makes the browser open the print dialog.
The vulnerability happens because the website takes data from the URL hash and uses it inside a jQuery selector without proper filtering. Because of that, the injected HTML can be executed by the browser.
After clicking Store, I clicked View exploit to test the payload.
When the exploit was opened, the browser showed the print page or print dialog.
At first, it looked like the page could not be clicked or used normally because the print dialog appeared. But this actually means the payload worked, because the print() function was executed.
After confirming that the payload worked, I went back to the exploit server page.
Then I clicked Deliver exploit to victim.
After the exploit was delivered, the victim browser opened the crafted page. The iframe loaded the vulnerable page, triggered the hashchange event, and executed the print() function.
After that, the lab status changed to Solved.
This lab shows how DOM XSS can happen when data from the URL hash is used directly inside a jQuery selector.
By using this payload:
<iframe src="https://YOUR-LAB-ID.web-security-academy.net/#" onload="this.src+='<img src=x onerror=print()>'"></iframe><iframe src="https://YOUR-LAB-ID.web-security-academy.net/#" onload="this.src+='<img src=x onerror=print()>'"></iframe>the exploit server loads the vulnerable page inside an iframe, changes the hash value, and triggers the hashchange event. The injected image fails to load, so the onerror event runs print().
From this lab, I learned that data from location.hash should not be directly used in jQuery selectors without proper validation or encoding.
Thanks for your attention.