XSS challenges have always been tricky kinds of challenges, especially the ones where your normal payload would never work and you have to go through different kinds of tags, attributes, and event handlers to craft your XSS payload. DOM-based XSS challenges are no different, the only difference is, you need to analyze a bit more to understand the kind of payload to be crafted to execute it.
Let’s get an overview of it. DOM stands for Document Object Model which is used to make any changes in the DOM of the browser. DOM works according to the user input which is the source and the element/object which consumes our input and performs actions accordingly is called the sink. So, if the user input contains the payload, and if the user input is consumed by a sink then the application might be vulnerable to DOM-based vulnerabilities.
In this article, I will be writing about my approach towards finding and exploiting DOM-based XSS by solving the labs here: https://spidersec.ninja/labs/index.html
Let’s get started:
We have a total of seven challenges and we will analyze each of them one by one. :)
Here, we have an input field. Let’s start by analyzing the input field first. I will add “shruti” and observe the reflection.
We see that the string “shruti” is getting reflected. Let’s review the source code to find out the reflection point.
The string “shruti” is not reflected in the source code. Also, we see the use of document.getElementById() , so it seems to be using DOM. Let’s Chrome DevTools and analyze the code again. In Chrome, select the string “shruti”, right-click and open inspect element. Then click on Sources. Similar steps can be followed on any other browser
Click on the line number 36 as shown in the screenshot above to set a breakpoint on it so that when the application hits this line it will pause its execution and wait for us to take action on it which can be used to step through each function calls after this line
Wait!! Why line 36???
Because, while analyzing the code, we observed that this line is the one which is asking for our input via the document,getElementById(). Now we need to figure out which function is consuming our input
You should see a red circle in front of the line which indicates the breakpoint
Next, we use the step function as marked in the image below to see the next steps which helps us to analyze and understand the code flow further.
And on clicking on the step function...
Here we will see our string “shruti”. So here we find our input source which as suspected is the document.getElementById().
Let’s move to the next step and see what happens.
The html variable on line 40 is responsible for reflecting our input on the browser because of innerHTML. So the innerHTML is our sink here and we know if our input consumed by sink it can possibly be vulnerable to DOM-XSS. So, let’s try a simple payload in the input.
But wait a minute, script tags do not get executed in innerHTML. So, let’s add our payload with an img tag, which will be
It seems that the img tag is getting executed but our alert function is not working. Let’s analyze our payload again in the debugger.
Let’s click on the step button and see the next step.
Here, we see that on line 37 the round brackets () are getting filtered out by js_filter0. So we need to come up with a payload without the round brackets. Let’s try this payload now
And boom we are done. We solved our first challenge :)We now understood how to use a debugger to analyze our code and look for the source and sink. We will follow similar steps throughout this article to solve the rest of the challenges.
In this challenge, we will perform the same steps as above to solve it. While debugging, we see that this challenge also uses document.getElementById as the source and innerHTML as the sink.
We again enter our string “shruti” and analyze the code in the debugger, as shown in the screenshots below.
So, in this challenge, we observe that the spaces are being filtered on line 37 in the js_filter1 variable.
So, on adding the normal payload - gives the following response.
And this is how it looks like in the debugger.
As we can see, the spaces are getting filtered over here, so we will have to use ta payload without spaces.
Let’s try -
And challenge 2 is done as well… :) kudos to us :)
Since we already understood how to analyze a DOM-based XSS with the help of debugger, we will directly jump to the step where we are adding our normal payload which is -
Let’s check in the debugger.
Let’s try to analyze and understand this
We will play with the “alert” keyword. Putting an “alert” keyword does not work as it is getting filtered out, we can use alternative ways such as ALERT in all capitals and see if it works.
Well!! It did not. It seems like the regex is strictly looking for the alert keyword
Let us try alalertert(1) instead of normal alert(1). Since the “alert” word is getting filtered out, we are using alert in between al and ert, so when the alert from the middle gets filtered out, we will have alert(1) again and as we can see in the code, there is no further check. So, let’s try this payload then -
We added our payload, and it did not work. Let us again check the debugger and analyze this further.
Our alalertert seems to be working fine. Let’s go to the next step and see.
If we observe, we will see that this time our payload is getting inside the tag attribute and for our payload to work, we will have to end this tag first to break the current context. So, our payload for this will be:
Let’s try this:
And it worked... :D
Analyzing the code as above, we will try adding - into the input field.
The img tag is getting executed but not the alert part. Let’s check in the debugger.
So, the round brackets ‘(‘ and the closing angular bracket '>' are being filtered out. If we remember, even in our first challenge, the round brackets were getting filtered out, so let us use `` instead of (). And hence, our payload becomes But in this case, the ‘>’ also is filtered out.
Let’s not add '>' in the end and see if it gets executed.
No, it is not getting executed. Let us add a comment at the end of the payload which will look something like this: . Here we are basically tricking the browser to think that the rest of the code is a comment and that it should execute the code before the comment as it is.
And we did it again. :)
Here is our challenge 5 and let’s start with analyzing the code with the help of the debugger again. :D But this time we observe something at the very first step. Let’s write “shruti” into our input field and analyze the code in the debugger.
Here, we can observe that there is an img tag in the code and it is getting executed. Let’s analyze this.
As we can see in the screenshot above, our input is already in the img tag. Also, we observe that the () and <> are getting filtered out, so we can craft our payload in this way-
So, let’s try our payload.
But our payload did not work. Let’s analyze the code further.
Let’s comment out the rest of the part and thus, our payload becomes -
And we did it again. :)
Following the same steps as above, we add our string and analyze it in the debugger to understand what is happening.
From the above code, we can see that almost every tag is filtered out, so adding img within img (Eg: imimgg) will also not help as there are a lot of checks involved. But if we observe carefully, we can still bypass the filter but using the img tag in capital letters.
Also () and > are getting filtered out.
And thus, our payload becomes -
We added // in the end, to comment out everything after our payload.
And it worked too. :D
Let’s analyze the final challenge :)
Let’s follow the above steps and analyze this challenge. :)
On adding the , we receive the following output.
As we see the screenshot below:
The code is replacing every character that we use for crafting our payload. But if we observe carefully, there is something very interesting there - unescape(html)
I, then, googled for the unescape function to understand what it does. I referred to this link, to analyze the function.
Here, we see that whenever we enter a string, and if it goes through the escape function it gives us output in an encoded format. And the unescape function, then url decodes it. Thus, if we put our payload in the same encoded format, it may get decoded by the unescape function used and also won’t get filtered out.
So, we craft our payload as -
Putting our payload into the input field:
And it worked. :D
We are done with all the challenges.
I hope you all learned something and let me know if you have any doubts or queries.