Testing for DOM-Based Cross Site Scripting
ID |
---|
WSTG-CLNT-01 |
Summary
DOM-based cross-site scripting is the de-facto name for XSS bugs that are the result of active browser-side content on a page, typically JavaScript, obtaining user input through a source and using it in a sink, leading to the execution of injected code. This document only discusses JavaScript bugs which lead to XSS.
The DOM, or Document Object Model, is the structural format used to represent documents in a browser. The DOM enables dynamic scripts such as JavaScript to reference components of the document such as a form field or a session cookie. The DOM is also used by the browser for security - for example to limit scripts on different domains from obtaining session cookies for other domains. A DOM-based XSS vulnerability may occur when active content, such as a JavaScript function, is modified by a specially crafted request such that a DOM element that can be controlled by an attacker.
Not all XSS bugs require the attacker to control the content returned from the server, but can instead abuse poor JavaScript coding practices to achieve the same results. The consequences are the same as a typical XSS flaw, only the means of delivery is different.
In comparison to other types of cross site scripting vulnerabilities (reflected and stored, where an un-sanitized parameter is passed by the server then returned to the user and executed in the context of the user’s browser, a DOM-based XSS vulnerability controls the flow of the code by using elements of the Document Object Model (DOM) along with code crafted by the attacker to change the flow.
Due to their nature, DOM-based XSS vulnerabilities can be executed in many instances without the server being able to determine what is actually being executed. This may make many of the general XSS filtering and detection techniques impotent to such attacks.
This hypothetical example uses the following client-side code:
<script>
document.write("Site is at: " + document.location.href + ".");
</script>
An attacker may append #<script>alert('xss')</script>
to the affected page URL which would, when executed, display the alert box. In this instance, the appended code would not be sent to the server as everything after the #
character is not treated as part of the query by the browser, but as a fragment. In this example, the code is immediately executed and an alert of “xss” is displayed by the page. Unlike the more common types of cross site scripting (reflected and stored in which the code is sent to the server and then back to the browser, this is executed directly in the user’s browser without server contact.
The consequences of DOM-based XSS flaws are as wide ranging as those seen in more well known forms of XSS, including cookie retrieval, further malicious script injection, etc., and should therefore be treated with the same severity.
Test Objectives
- Identify DOM sinks.
- Build payloads that pertain to every sink type.
How to Test
JavaScript applications differ significantly from other types of applications because they are often dynamically generated by the server. To understand what code is being executed, the website being tested needs to be crawled to determine all the instances of JavaScript being executed and where user input is accepted. Many websites rely on large libraries of functions, which often stretch into the hundreds of thousands of lines of code and have not been developed in-house. In these cases, top-down testing often becomes the only viable option, since many bottom level functions are never used, and analyzing them to determine which are sinks will use up more time than is often available. The same can also be said for top-down testing if the inputs or lack thereof is not identified to begin with.
User input comes in two main forms:
- Input written to the page by the server in a way that does not allow direct XSS, and
- Input obtained from client-side JavaScript objects.
Here are two examples of how the server may insert data into JavaScript:
var data = "<escaped data from the server>";
var result = someFunction("<escaped data from the server>");
Here are two examples of input from client-side JavaScript objects:
var data = window.location;
var result = someFunction(window.referrer);
While there is little difference to the JavaScript code in how they are retrieved, it is important to note that when input is received via the server, the server can apply any permutations to the data that it desires. On the other hand, the permutations performed by JavaScript objects are fairly well understood and documented. If someFunction
in the above example were a sink, then the exploitability in the former case would depend on the filtering done by the server, whereas in the latter case it would depend on the encoding done by the browser on the window.referrer
object. Stefano Di Paulo has written an excellent article on what browsers return when asked for the various elements of a URL using the document and location attributes.
Additionally, JavaScript is often executed outside of <script>
blocks, as evidenced by the many vectors which have led to XSS filter bypasses in the past. When crawling the application, it is important to note the use of scripts in places such as event handlers and CSS blocks with expression attributes. Also, note that any off-site CSS or script objects will need to be assessed to determine what code is being executed.
Automated testing has only very limited success at identifying and validating DOM-based XSS as it usually identifies XSS by sending a specific payload and attempts to observe it in the server response. This may work fine for the simple example provided below, where the message parameter is reflected back to the user:
<script>
var pos=document.URL.indexOf("message=")+5;
document.write(document.URL.substring(pos,document.URL.length));
</script>
However, it may not be detected in the following contrived case:
<script>
var navAgt = navigator.userAgent;
if (navAgt.indexOf("MSIE")!=-1) {
document.write("You are using IE as a browser and visiting site: " + document.location.href + ".");
}
else
{
document.write("You are using an unknown browser.");
}
</script>
For this reason, automated testing will not detect areas that may be susceptible to DOM-based XSS unless the testing tool can perform additional analysis of the client-side code.
Manual testing should therefore be undertaken and can be done by examining areas in the code where parameters are referred to that may be useful to an attacker. Examples of such areas include places where code is dynamically written to the page and elsewhere where the DOM is modified or even where scripts are directly executed.
Remediation
For measures to prevent DOM-based XSS, see the DOM-based XSS Prevention Cheat Sheet.