Solution for PortSwigger’s Academy: Stored DOM XSS
The following is my documentation on PortSwigger’s Academy labs.
End Goal: Exploit a stored DOM vulnerability and call the alert() function in the comments
In this lab, we are asked to perform a stored XSS. This is something we have performed before, but this time our comments are being filtered, so we’ll have to go through a few more steps than last time before we can craft our payload.
Investigating the target site
Our first step, as always, is to see what’s going on in the back end so that we can have a better understanding of what’s going on. After inspecting the page we start looking for any instances of the script tag.
We quickly find our first instance: <script src="resources/js/loadCommentsWithVulnearbleEscapeHtml.js">
This informs us that our page is calling for an external JavaScript document. They have been kind enough to label that document to let us know exactly what it does. Next, we’ll go to our Network tool in Firefox’s Web Development Tools and see if we can find that document.
After finding our document we select it and reload the page, making sure to check our Response tab afterwards.
Looks like there is some bad news. Line 12 and line 13 are going to give us some grief. function escapeHTML(html)
has the following: return html.replace('<', '<').replace('>', '>');
Even without knowing JavaScript, you can probably intuit what’s going on here. Our angle brackets are automatically going to be swapped out with the HTML “greater than” and “less than” codes, essentially neutralizing our attempts to inject our attack in the comments section. When we inspect a post with a comment section on the target site, we can see this function being called in the back end.
So how do we escape this pesky function?
What is replace()?
Let’s head on over to MDN web docs and take a closer look at this function.
“This method does not mutate the string value it’s called on. It returns a new string.”1
This part we intuited, but the next part is what will excite us:
“A string pattern will only be replaced once. To perform a global search and replace, use a regular expression with the g flag, or use replaceAll() instead.”2
This is precisely why being curious and doing research is important in hacking. We just found out, and with little effort, that the “replace” function will only return on what it was called on once. This means that if we have another instance of the strings that it is responsible for swapping out, it will leave the second instance alone.
Escaping with additional angle brackets
Now that we’ve investigated the page and researched its methods, it’s time to come up with a plan and write our payload.
Previously we injected our attack into the “Website” field of the comment section. So let’s take a quick look at that to see if it is still a viable option. Maybe we can place our attack in a link for an unsuspecting user to click.
Looking at it tells us that this might be more trouble than it is worth. There is just too much to escape here, so let’s move on and try the “Name” field. For our attack, we’ll use the following payload:
1nforography<><img src=1 onerror=alert(1)>
Remember, we are using “<>” before the rest of the payload knowing that the function will only replace these characters once, allowing us to use those brackets again without trouble.
All we have to do now is hit “Post Comment”, return to the page, and…
Our alert populates, which means that we solved this lab!
We can see our attack working when we inspect the element here:
Potential impact of a stored XSS attack
“If an attacker can control a script that is executed in the victim’s browser, then they can typically fully compromise that user.”3
Clearly, the coder for our target page knew that an XSS attack can be devastating. They did their best to make sure that we could not inject one, after all. What could they have done better?
Prevention
“…to safely embed user input inside an event handler, you need to deal with both the JavaScript context and the HTML context. So you need to first Unicode-escape the input, and then HTML-encode it…”4
The coder for our target site converted our angle brackets into HTML entities (< and >, respectfully), but they could have gone further and made sure that the angle brackets were also Unicode-escaped for JavaScript. They could have done this by swapping out our angle brackets “<” and “>” with “\u003c” and “\u003e”.
Further, instead of using the “replace” function, they could have used replaceAll()
instead.
Notes
I used the following resources in solving this lab:
- MDN web docs replace() description. ↩︎
- MDN web docs replace() description. ↩︎
- PortSwigger: Impact of stored XSS attacks ↩︎
- PortSwigger: How to prevent XSS ↩︎
Updates:
1.1.24
After doing some more research and checking PortSwigger’s own solution I found that most people are injecting their code in the “Comment” field.
The results are the same, but in retrospect, this is the more obvious field to write in.
Share this:
Filed under: Cybersecurity,Pentesting,PortSwigger Academy,XSS attack - @ January 1, 2024 9:14 pm