June 21, 2026
Cross-site scripting 6 (APPRENTICE)
Lab 8 - DOM XSS in jQuery selector sink using a hashchange event.
Nadia
3 min read
Lab 8 - 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 function to auto-scroll to a given post, whose title is passed via the location.hash property. To solve the lab, deliver an exploit to the victim that calls the print() function in their browser.
Solution
1.Step 1: You need to click the orange button that says "Access the Lab" on the home page.
- Step 2: Once you've successfully logged into the lab, try accessing the URL by adding a hash fragment such as #123 (this is used to trigger the vulnerable jQuery selector, proving that the input from the hash is used directly in the jQuery selector before it is later filled with an XSS payload).
Then right-click and select "Inspect", you'll see an error like "Uncaught TypeError: can't access property 'scrollIntoView', post.get(…) is undefined." This confirms that the application uses jQuery to retrieve and process the value of location.hash and inserts it directly into the $( ) function (jQuery selector) without filtering or validating it first. This constitutes a vulnerable sink (in the context of DOM XSS, a sink refers to a point in the code where untrusted data is ultimately executed or processed in a dangerous manner). Since the $( ) function in older versions of jQuery can execute HTML passed into it, the $( ) function here is considered a sink vulnerable to XSS.
- Step 3: Next, navigate to the URL with the fragment #Hobbies, and in the console, manually inspect
window.location.hash; Output: "#Hobbies"
window.location.hash.slice(1); Output: "Hobbies"
Here, we can see that jQuery executes a query like $('section.blog-list h2:contains(Hobbies)'), which proves that the input from the hash is directly inserted into the jQuery selector without any prior sanitization.
4. Step 4: Still in the console, let's perform further verification by manually testing the selector:
var post = $('section.blog-list h2:contains(Hobbies)');
post.get(0); (Returns the
element)
if (post) { console.log('true') } (Output: true)
This confirms that the hash value is used directly as a jQuery selector, meaning we can inject HTML/JS via a URL fragment.
- Step 5: Now let's try injecting a simple payload via a hash, such as #
. In the console, we can see that jQuery is attempting to process $('section.blog-list h2:contains(
)') . The
element was successfully added/inserted into the page structure (DOM) via mynode.appendChild(post) (meaning the post element containing
was appended as a child of the existing mynode element on the page), and the onerror event was triggered, proving that the XSS is working.
- Step 6: Next, open Burp Suite and use the Burp browser to access the URL with the payload https://0a4d005a04a332c680abd5e3005200d5.web-security-academy.net/#
. In the Network tab, you'll see a request to the lab endpoint with a 504 Gateway Timeout status (which is actually normal for a lab environment), but what's important here is that the pop-up alert has appeared with the lab's domain, confirming that the XSS attack was successfully executed.
- Step 7: Next, we need to go to the Exploit Server page by clicking the "Go to exploit server" button, and create a payload that calls print() according to the lab's objective: . Once done, click 'Store' and "Deliver exploit to victim." Once the lab is successfully completed, the browser will display the message "Congratulations, you solved the lab!" and the status will change to "Solved."