Main Content

Heya - HollyGraceful here, I make all of this content in my spare time, like it? Please support me :)
You can donate via Bitcoin or Patreon!

An Introduction to DOM XSS

Document Object Model Based Cross-Site Scripting (DOM Based XSS) is a type of Cross-site Scripting where instead of the payloads being stored or reflected by the remote web server and appearing in the response HTML the payload is instead stored in the DOM and processed insecurely by JavaScript. For those unfamiliar with what the DOM is, a short and fairly untechnical overview is available here.

The impact, and exploitation of DOM-XSS, is essentially the same as reflected or stored however the detection is a little different, as you can’t simply check the server responses and build up a payload. For example if you’re using Burp Suite for testing Burp doesn’t parse or execute JavaScript and therefore it won’t be too much help there. (It will however look for DOM-XSS through static analysis and pick up on issues such as location.hash ending up in document.write).

This is the idea of “sinks” and “sources”, where a vulnerability may occur if an attacker is able to control a source and the data retreived makes it into a sink without filtering, validation or encoding. The data may be processed and changed in the centre as long as a payload can be snuck along too. Essentially we concentrate on areas where user input may find its way into potentially vulnerable functions.

It may be useful to remember that Firefox can show the DOM-Source by highlighting an area of the page and selecting “View Selection Source”. Generally I will manually review JavaScript and try and link between areas of user input and potentially dangerous functions. Here I’ll try to demonstrate the concept through a real-world example found on a recent Penetration Test. Hopefully this will highlight the concepts as well as some of the difficulties in finding these issues:

During a recent Penetration Test I can across an interesting piece of JavaScript which turned out to be vulnerable, so I’ve cleaned it up, anonymised it and present it here as a vulnerable example. In the actual application this was a collection of four functions being loaded from different locations and was a little more complex than what you see here, I’ve simply reduced the complexity so that it doesn’t distract too much from the general idea. The idea here was that I tied a source to a sink and that led to an exploit.

The following code is similar to what I found during my Penetration Test:

1. <html><body>
2. <script>
3.   function OnLoad() {
4.     var foundFrag = get_fragment();
5.     return foundFrag;
6.   }
8. function get_fragment() {
9.   var r4c='(.*?)';
10.   var results = location.hash.match('.*input=token('+r4c+');');
12.   if(results){
13.     return (unescape (results[2]));
14.   } else {
15.     return null;
16.   }
17. }
19.   display_session = OnLoad();
20.   document.write("Your session ID was: "+display_session+"<br><br>")</script>
21.   An error occurred...
22. </body><html>

First of all I noticed the function document.write() executing with a parameter containing get_fragment(). I traced document.write through display_session to OnLoad() which executes get_fragment() which takes input from the URL. In reality that link between user input and vulnerable function was a little more blurry, with some additional processing being done to “results”, however you can see the idea here. Through the application scripts we try and tie together user inputs and vulnerable functions.

If we can get unfiltered user input into document.write() we can deface the web application or potentially steal confidential data. Location.hash is being used as input to that function so let’s start there because that’s user input (an attacker could craft a malicious link with a payload in the fragment). For those unfamiliar with JavaScript the line in question is number 10. It’s taking location.hash which is the contents of the URL after the “fragment”, that’s after the # at the end of the URL, such as:

It’s taking the word “foobar” from above and applying a Regular Expression to it. The expression looks for the string “input=token” anywhere in the fragment and reads from that string up to a semi-colon character (that’s what r4c is doing, it’s a capture group that captures any character using the multiple wildcard “.*”). Therefore in this case, if I wanted to execute a simple XSS proof-of-concept such as alert(1) I could play this in the URL fragment after the string input=token but before a semicolon like this:<script>alert(1)</script>;

With this, the script would find intput=token and read from there up to the semi-colon, then place that input within the document.write function and the following happens:

A screenshot showing a successful DOM-based cross-site scripting attack.

A simple XSS proof-of-concept, here however you might thing that a more complex payload would not be possible as the application stops reading when it spots a semi-colon and so something as simple as alert(1);alert(2) would not be possible, however thanks to the use of unescape() on the returned data I can use a payload like this:<script>alert(1)%3balert(2)</script>;

Where I am replacing the ; character with its URI encoded equivalent!

No I can write my malicious JavaScript payload and craft a link to send to a victim just like I would with standard reflected cross-site scripting! So all in all detection and payload generation is a little trickier usually, but the act of exploitation and the impact is just the same! Detection is harder because I can’t use a tool like Burp Suite to view the modified responses (as it’s all handled client site by the browser). However by using tools like firebox, by using the developer console, or by statically analysing the JavaScript we can still pick up on these issues simply.


It’s worth noting that modern browsers have made steps to restrict the exploit-ability of issues like reflected XSS and DOM-Based XSS. However there are still things that can be done by developers. Generally speaking HTML Entity encoding none alphanumeric user input as it is rendered to the browser will effectively block an attackers ability to execute scripts in the malicious ways described above whilst preserving the functionality of the application.

It’s not enough to block keywords such as “script” (and certainly alert!), it is more important to block the characters required to write effective JavaScript, such as:

( ) ; " '

I build on this idea in more detail within my article about Web Application Defence, which can be found here.



This article is part of a series, want to read more?
What is Cross-site Scripting?
Cross-site Scripting, Lesson 2: Contexts
An Introduction to DOM-XSS
An Introduction to Content-Security-Policy
Life After the alert(1)