PortSwigger's "DOM XSS in innerHTML sink using source location.search" Walkthrough

This is the second of the three Apprentice-level DOM-based XSS Labs from Portswigger. Before we get started, you’ll need a Portswigger Academy account. This blog post shows how to solve the lab manually.

After logging in, head over to the lab, located at https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-innerhtml-sink. You can find this through the Academy learning path list, or linked within the DOM-based XSS blog post.

Challenge Information

Click the “Access the Lab” button and you will be taken to a temporary website that is created for your account. This will be in format https://<random string here>.web-security-academy.net/.

This is a DOM-based XSS attack. This means that our malicious input will be passed to a “sink” that supports dynamic code execution through a Javascript, JQuery, etc. function call.

The website looks like this:

If we search for “hi” (or any other query), then look at the Dev Tools Elements view, we can see this script:

function doSearchQuery(query) {
    document.getElementById('searchMessage').innerHTML = query;
}
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
    doSearchQuery(query);
}

This will see if there’s a query in the location.search variable (our URL input), and if so, will do document.getElementById to get the element with ID searchMessage. Then it will set its innerHTML to our query.

This is how the contents of <span id="searchMessage"> are set to “hi”.

OWASP has a series of cheatsheets and the DOM-based XSS prevention one specifically calls out innerHTML as a dangerous operation, since HTML tags can be injected.

Lab Solution

We want to trigger an alert() and we have the ability to inject content (including HTML tags, most likely) into the page.

Let’s try a (search query) payload of:

hi<script>alert(1)</script>

The script tags appear in the HTML but do not trigger an alert:

This is true whether or not you include the hi, whether you try to close the span tag, and so on.

If we try a different route (particularly one that relies on an error condition), we could use a payload like:

<img src=x onerror=alert(1)>

Submit this as a search query, and you should get an alert:

The HTML looks like this:

The reason this works is because the img src is invalid (x), which throws an error. Then the onerror event is triggered, and our payload instructs the browser to call alert() when this happens.