Update April 2021: Some changes to the heroku Juice Shop app have broken this demo. The script payload no longer works for Juice shop, however there are other XSS payloads that do work, such as payloads that use onerror attribute of img tag. I am not going to update the screenshots below, but I have updated the malicious server code so that one can still see the proof-of-concept.
In my employment, I am responsible for making sure developers produce secure code, and security education is a key part of reaching this goal. There are many ways that one can approach security education, but one thing that I have found is that developers really appreciate seeing how attacks are performed and exploited in practice, so hacking demonstrations and workshops have so far been a hit.
In doing these demonstrations, I have found two intentionally insecure test sites that have very similar cross site scripting (XSS) vulnerabilities – Altoro Mutual and OWASP Juice Shop. In fact, on the surface the vulnerabilities look identical, but under the hood they are different: Altoro Mutual has a reflected XSS whereas the Juice Shop has DOM-based XSS. It turns out that DOM-based XSS is much more convenient to demonstrate in a corporate environment for a few reasons, and in general more favourable to an attacker.
In this blog, I explain why and show you how to build a really cool demo of exploiting the OWASP Juice Shop DOM-based XSS. You will need a malicious server for the demo, but I have the code and a sample server all ready for your convenience. The demo uses the malicious server to steal the victim’s cookie, and from there we are able to retrieve his password. Full details below.
If you are not familiar with XSS, this section is for you. Everyone else, skip to the good stuff below.
The most famous XSS attack that I know of is the “Samy is my Hero” MySpace worm. Although not as malicious as it could have been, Samy Kamkar created a script on MySpace that would send Samy a friend request and copy the script on the victim’s MySpace profile, along with displaying the string “but most of all, samy is my hero” on their profile. Within 20 hours, Samy had over one million friend requests.
The Samy worm was a persistent XSS, which means the script was permanently stored in the database. Persistent XSS vulnerabilities are the worst kind because everyone becomes vulnerable just by visiting the site.
Two other XSS types are reflected and DOM-based, which you will learn about in good detail by reading this blog. These are both less severe than persistent because the victim needs to be tricked into hitting the vulnerability (i.e. social engineering), but we will see that the consequences can be pretty bad for those who fall victim. I have not seen any comparison of the severity of the two, but below I make the argument that DOM-based is more serious than reflected.
The Two Vulnerabilities
Let’s start with Altoro Mutual. In the search bar, you can type a script, for example a simple alert(“hello”) surrounded by <script> open and close tag:Upon hitting enter, you might see that the script executes (reflected XSS):However, you also might not see this. For example, if you try running it in a corporate environment, then the company proxy might have stopped it (one of the problems I ran into). If you use Chrome, you might have found that Chrome blocked it (one of the problems that the audience ran into when trying it — see screenshot below). There are workarounds for both cases, but it blunts the point of the severity of the issue when it only works in special cases.Now let’s try the exact same thing in OWASP Juice Shop. Type in your script in the search bar:Upon hitting enter, I’m quite sure you will see this (DOM-based XSS): In my case, neither the corporate proxy nor Chrome blocked it. There is a good reason for that.
Let’s first confirm that the Altoro Mutual vulnerability is really reflected XSS and the OWASP Juice Shop is really DOM-based. We do that by sending communications through your favourite intercepting proxy. For Altoro Mutual, it looks like this:We see that the script entered in the search bar was reflected back as part of the html.
Now do the same with the Juice Shop:It was not reflected back.
r.searchQuery = t.trustAsHtml(e.search().q)
The trustAsHtml is an Angular method that does not do the default escaping.
Corporate proxies and browsers (Firefox currently does not) can easily block reflected XSS by checking whether the string sent contains a script and looking to see if the exact same string comes back. They can stop the hack as it is happening.
The same protection does not happen for DOM-based XSS. The attack happens entirely in the browser, which prevents corporate proxies from stopping it. It is a lot trickier for a browser to stop this type of attack than it is for one to stop reflected XSS.
Demonstrating Real Exploitation of DOM-based XSS
Although one can explain to developers and business people the dangers of XSS, nothing is more convincing than a real demonstration, and the OWASP Juice Shop is perfect for this. In our demonstration, we need a malicious server. I have created one for you that you can deploy in a couple minutes on Heroku: see github link. To speed things up, you can use my temporary server (to be taken down later).
In our demo, assume a user has logged into the OWASP Juice Shop. The user visits a malicious website that has a link promising free juice if you click it. Clicking the link triggers an XSS that takes the victim’s cookie and sends it to a “/recorddata” endpoint on the malicious server. From there, the attacker can hit a “/dumpdata” endpoint to display the captured cookies. The cookies contain JWTs, which when decoded, contain the MD5 hash of the user password (very dumb design, but I have seen worse in real code that was not intentionally insecure). Using Google dorking, the MD5 hash can be inverted to recover the victim’s password. Full details below.
First head over to the OWASP Juice Shop and click Login. From there, you can register. See Figure below:From there you create the account of your victim user:Next, the victim logs in:All is fine and dandy, until somebody tells the victim of a website that offers free juice to OWASP Juice Shop customers. What could be better! The victim rushes to the site (for our temporary deployment, link is: https://frozen-crag-69213.herokuapp.com/freejuice):Upon clicking the link, the DOM-based XSS is triggered. A nontechnical user would likely not understand that a script has executed from the malicious site. In fact, in this case, the script has taken the victim’s cookie and sent it to the malicious website. The malicious website has a “/recorddata” endpoint that records the cookie in a temporary file (a more serious implementation would use a database).Our malicious server also has a “/dumpdata” endpoint for displaying all the captured cookies (here for our temporary server).
Inside the cookie is a JWT. Let’s copy that JWT into our clip board:And now head over to jwt.io where we can paste the token in and decode it:Amazing! The username and password are in the cookie. But that’s not the real password, so what is it? Let’s Google it:And clicking the first link, we find out that it was the MD5 hash of the password. The real password is revealed in the link:
I have found that rather than teach boring coding guidelines, developers much prefer to see security vulnerabilities and how they are exploited in practice. Once they learn the problems, they find a way to code them properly. You know, developers are very good at using Google to find answers once they understand the problem they are trying to solve.
The OWASP Juice Shop is a great website for demonstrating security vulnerabilities, but in some cases you need to add your own parts to make the demo complete. In this blog, we provided the malicious server that executes a DOM-based XSS when a user clicks a link, which allows the attacker to recover the user’s password. The Juice Shop DOM-based XSS is much more convenient to demonstrate exploitation than the reflected XSS in Altoro Mutual.
For those familiar with CVSS for rating security vulnerabilities, the rating includes an attack complexity parameter to indicate whether special conditions need to exist for the attack to be successful. For reflected XSS, two special conditions were mentioned above: the victim’s browser would need to not defend against the attack (most browsers will stop it) and the victim needs to be in an environment where the attack would not be blocked. For DOM-based, it appears that there are no special conditions (browsers will not block the DOM-based attacks, and environment is irrelevant for this attack to work). Hence, DOM-based XSS are more favourable to attackers than reflected XSS, the difference being the complexity of pulling off the attack. Therefore, DOM-based XSS is more severe than reflect XSS, but less severe than persistent.