By now you have an understanding of Stored XSS attack and Reflected XSS attack, and some measures to prevent it. Now we will look into the last type of XSS attack, DOM based XSS attack. In the end, I will conclude with best practices to follow, testing XSS and references for reading.
3.DOM based Cross-site scripting
The difference in DOM based XSS from the other type of XSS type is that, the attack happens only via client-side application. In Stored and Reflected type of XSS there are some server-side requests involved.
DOM based XSS mainly happens by injecting malicious javascript via URI fragments and can execute runtime in browser.
URI fragments, how they work
What is this URI fragment ? Have you noticed when you click on certain links for a documentation, it navigates and scrolls down right to the section. Well, that's because the link looks like this http://www.example.org/foo.html#bar
and on clicking it the page scrolls down to the section "bar" which has the element with id="bar"
.
The URI fragment is the optional part of the URL after the #
sign.
Popular frameworks like React, Vue and Angular make use of URI fragment for a different reason. In Single Page Apps (SPA), whenever the user refreshes or reload the page, the initial state of the content is lost and therefore a rendering blink can be noticed. To avoid losing the state of the page, these framework uses URI fragment at the end of the URL.
For example, if a page uses pagination, the SPA may make use of the URI fragment to indicate the page number and will update the number as the user goes to the next page. Now, even if user refreshes the browser, the JavaScript code can interpret the content of the URI fragment, and load the relevant page visited previously.
The URI fragments does not interact with the server-side, therefore securing server-side code will not prevent DOM based XSS attacks and will not be recorded in server logs.
The attack happens when an attacker crafts malicious javascript in the URI fragment and tricks a user to click it, and the attack is launched when the Javascript code interprets the content of the fragments unescaped.
The fragment value including the hash can be accessed in the code by the method window.location.hash
. These are usually not percent-decoded.
Defence #1 - You guessed it ! Escape dynamic content from URI fragments
You need to escape anything that comes out of URI fragments before using that value in HTML or in Javascript.
Examples of vulnerable code
<script>
var x = location.hash.split("#")[1];
document.write(x);
</script>
<script>
var x = '<%= taintedVar %>';
var d = document.createElement('div');
d.innerHTML = x;
document.body.appendChild(d);
</script>
There are a lot of scenarios and contexts that you need to look out for, and it's not easy. Using a web application development framework for your frontend application, will lower the risks of XSS attacks due to its auto-escaping and context-awareness. If not, make use of OWASP Enterprise Security API (ESAPI) that makes it easier to write low-risk application code.
The npm library for ESAPI is node-esapi which includes functions for encoding HTML, HTML attributes, Javascript and CSS.
Examples of usage of this library is as below
var ESAPI = require('node-esapi');
element.innerHTML = "<%=ESAPI.encoder().encodeForJS(ESAPI.encoder().encodeForHTML(untrustedData))%>";
element.outerHTML = "<%=ESAPI.encoder().encodeForJS(ESAPI.encoder().encodeForHTML(untrustedData))%>";
var ESAPI = require('node-esapi');
document.write("<%=ESAPI.encoder().encodeForJS(ESAPI.encoder().encodeForHTML(untrustedData))%>");
document.writeln("<%=ESAPI.encoder().encodeForJS(ESAPI.encoder().encodeForHTML(untrustedData))%>");
Best practices for preventing XSS
JavaScript:
Many XSS vulnerabilities are caused by passing user data to Javascript execution sinks (browser mechanisms that will execute scripts from their input). Such APIs include .innerHTML
, document.write
and eval()
.
When user-controlled data (in the form of location.*
, document.cookie
or JavaScript variables containing user data) is returned by the server, calling such functions can lead to XSS.
JSON:
Make sure you apply proper escaping (including HTML-escaping of characters such as <
and >
). Do not allow user-supplied data to be returned as the first part of the response (as often happens in JSONP). Do not use eval()
to parse the data.
HTML sanitization:
If you need to support user-supplied markup such as images or links, look for technologies that support HTML sanitization. For example, sanitize-html
Testing for XSS
- If you want to check your code for DOM based XSS vulnerability, try pasting your code in semgrep playground.
- Manual testing
- Test wherever your app handles user input. A good test string to insert in the input fields is
>'>"><img src=x onerror=alert(0)>
.If your application doesn't correctly escape this string, you will see an alert and will know that something went wrong. - Wherever your application handles user-supplied URLs, enter
javascript:alert(0
ordata:text/html
,<script>alert(0)</script>
. - Create a test user profile with data similar to the test strings above. Use that profile to interact with your application. This can help identify stored XSS bugs.
- Unit tests to verify correct escaping or sanitization in inputs and other crucial parts of your code, make sure URL redirects start with
https
, verify that any unsupported markup is escaped. - Security scanning automated tools like the ones listed here.
Note: Follow this guide for the testing other alternative kinds of XSS attacks XSS Filter Evasion Cheat Sheet
References
For more detailed ways of prevention against XSS attacks, read through the following
- XSS Experimental Minimal Encoding Rules
- DOM based XSS Prevention Cheatsheet
- HTML5 Security Cheatsheet
- Cross Site Scripting Prevention Cheat Sheet
Here ends the posts for Cross-site scripting attacks. Hope you got a better understanding about XSS now and take necessary measures to protect your code.