1. Web Security Academy
  2. Cross-site scripting
  3. DOM-based

DOM-based cross-site scripting

DOM-based XSS (also known as DOM XSS) arises when an application contains some client-side JavaScript that processes data from an untrusted source in an unsafe way, usually by writing the data to a potentially dangerous sink within the DOM.

Source

A source is a JavaScript property that contains data that an attacker could potentially control. An example of a source is location.search, which reads input from the query string.

Sink

A sink is a function or DOM object that allows JavaScript code execution or rendering of HTML. An example of a code execution sink is eval, and an example of an HTML sink is document.body.innerHTML.

To deliver an XSS attack, you need to place data into a source, such that it is propagated to a sink and causes execution of arbitrary JavaScript.

Testing for DOM-based cross-site scripting

The majority of DOM-based cross-site scripting vulnerabilities can be found quickly and reliably using Burp Suite's web vulnerability scanner. To test for DOM-based cross-site scripting manually, you generally need to use a browser with developer tools, such as Chrome. You need to work through each available source in turn, and test each one individually.

Testing HTML sinks

To test for DOM XSS in an HTML sink, place a random alphanumeric string into the source (such as location.search), then use developer tools to inspect the HTML and find where your string appears. Note that the browser's "View source" won't work for DOM XSS testing because it doesn't take account of changes that have been performed in the HTML by JavaScript. In Chrome's developer tools, you can use Control+F (or Command+F on MacOS) to search the DOM for your string.

For each location where your string appears within the DOM, you need to identify the context. Then based on this context, you need to refine your input to see how it is processed. For example, if your string appears within a double quoted attribute then try to inject double quotes in your string to see if you can break out of the attribute.

Note that browsers behave differently with regards to URL-encoding, Chrome, Firefox, and Safari will URL-encode location.search and location.hash, while IE11 and Microsoft Edge (pre-Chromium) will not URL-encode these sources. If your data gets URL-encoded before being processed, then an XSS attack is unlikely to work.

Testing JavaScript execution sinks

Testing JavaScript execution sinks for DOM XSS is a little harder. With these sinks, your input doesn't necessarily "appear" anywhere within the DOM so you can't search for it. Instead you'll need to use the JavaScript debugger to determine whether and how your input is sent to a sink.

For each potential source, such as location, you first need to find cases within the page's JavaScript code where the source is being referenced. In Chrome's developer tools, you can use Control+Shift+F (or Command+Alt+F on MacOS) to search all the page's JavaScript code for the source.

Once you've found where the source is being read, you can use the JavaScript debugger to add a break point and follow how the source's value is used. You might find that the source gets assigned to other variables, and you'll then need to track these variables until you reach a sink. You can use the search function again to track those variables to see if they are passed to a sink. When you find a sink that is being assigned data that originated from the source, you can use the debugger to inspect the value by hovering over the variable to show its value before it is sent to the sink. Then, as with HTML sinks, you need to refine your input to see if you can deliver a successful XSS attack.

Exploiting DOM XSS with different sources and sinks

In principle, an application is vulnerable to DOM-based cross-site scripting if there is an executable path via which data can propagate from source to sink. In practice, different sources and sinks have differing properties and behavior that can affect exploitability, and determine what techniques are necessary. Additionally, the application's scripts might perform validation or other processing of data that must be accommodated when attempting to exploit a vulnerability. There are a variety of sources and sinks that are relevant to DOM-based vulnerabilities.

The document.write sink works with script elements, so you can use a simple payload such as:

document.write('... <script>alert(document.domain)</script> ...');

Note, however, that in some situations the content that is written to document.write includes some surrounding context that you need to take account of in your exploit. For example, you might need to close some existing elements before using your JavaScript payload.

The innerHTML sink doesn't accept script elements on any modern browser, nor will svg onload events fire. This means you will need to use alternative elements like img or iframe. Event handlers such as onload and onerror can be used in conjunction with these elements. For example:

element.innerHTML='... <img src=1 onerror=alert(document.domain)> ...'

If a JavaScript library such as jQuery is being used, look out for sinks that can alter DOM elements on the page. For instance, the attr function in jQuery can change attributes on DOM elements. If data is read from a user-controlled source like the URL and then passed to the attr function, then it may be possible to manipulate the value sent to cause XSS. For example, here we have some JavaScript that changes an anchor element's href attribute using data from the URL:

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

You can exploit this by modifying the URL so that the location.search source contains a malicious JavaScript URL. After the page's JavaScript applies this malicious URL to the back link's href, clicking on the back link will execute it:

?returnUrl=javascript:alert(document.domain)

If a framework like AngularJS is used, it may be possible to execute JavaScript without angle brackets or events. When a site uses the ng-app attribute on an HTML element, it will be processed by AngularJS. In this case AngularJS will execute JavaScript inside double curly braces that can occur directly in HTML or inside attributes.

DOM XSS combined with reflected and stored data

Some pure DOM-based vulnerabilities are self-contained within a single page. If a script reads some data from the URL and writes it to a dangerous sink, then the entire vulnerability exists within that page.

In other cases, the vulnerability involves some server-side reflection or storage of the input that is handled unsafely by the client-side script:

  • In a reflected+DOM vulnerability, the server-side application processes data from the request, and echoes the data into the response. The reflected data might be placed into a JavaScript string literal, or a data item within the DOM, such as a form field. A script on the page then processes the reflected data in an unsafe way, ultimately writing it to a dangerous sink.
  • In a stored+DOM vulnerability, the server-side application receives data from one request, stores it, and then includes the data in a later response. A script within the later response then processes the data in an unsafe way.
web-security-academy-white

Want to track your progress and have a more personalized learning experience? (It's free!)

Sign up Login
back-to-top