Protecting Your Users Against DOM-Based XSS attacks

Cross-site scripting (XSS) is one of the most common ways hackers attack websites. XSS vulnerabilities permit a malicious user to execute arbitrary chunks of JavaScript when other users visit your site.

XSS is the most common publicly reported security vulnerability, and part of every hacker’s toolkit.

Risks

Prevalence Rare
Exploitability Easy
Impact Harmful

DOM-based XSS attacks have all the risks associated with the other types of XSS attack, with the added bonus that they are impossible to detect from the server side. Any page that uses URI fragments is potentially at risk from XSS attacks.

Protection

Protecting against DOM-based XSS attacks is a matter of checking that your JavaScript does not interpret URI fragments in an unsafe manner. There are a number of ways to ensure this.

Use a JavaScript Framework

Frameworks like AngularJS and React use templates that makes construction of ad-hoc HTML an explicit (and rare) action. This will push your development team towards best practices, and make unsafe operations easier to detect.

AngularJS

In Angular any dynamic content written out in curly brackets will automatically be escaped, so the following is safe:

  <div>{{dynamicContent}}</div>

Be wary of any code that binds dynamic content to the innerHTML attribute since that will not be escaped automatically:

  <div [innerHTML]="dynamicContent"></div>
  <div innerHTML="{{dynamicContent}}"></div>
React

In React any dynamic content written out in curly brackets will automatically be escaped, so the following is safe:

render() {
  return <div>{dynamicContent}</div>
}

React allows you write out raw HTML by binding content to the dangerouslySetInnerHTML property, which is named to remind you of the security risk! Watch out for any code that looks like the following:

render() {
  return <div dangerouslySetInnerHTML={ __html: dynamicContent } />
}
Audit Your Code Carefully

Sometimes a full JavaScript framework is too heavyweight for your site. In that case, you will need to regularly conduct code reviews to spot locations that reference window.location.hash. Consider coming up with agreed coding standards on how URI fragments are to be written and interpreted, and centralize this logic in a core library.

If you use JQuery, carefully check any code that uses the html(...) function. If you are constructing raw HTML on the client-side on the back of untrusted input, you may have a problem, whether the input comes from a URI fragment or not. Use the text(...) function whenever possible.

If you are using direct the native DOM APIs, avoid using the following properties and functions:

Instead, set text content within tags wherever possible:

Parse JSON Carefully

Do not evaluate JSON to convert it to native JavaScript objects - for example, by using the eval(...) function. Instead use JSON.parse(...).

Detect Unsafe Code Using Development Tools

The Burp Suite, produced by the security firm PortSwigger, can be used to to detect DOM-based vulnerabilities.

Don’t Use URI Fragments At All!

The most secure code is the code that isn’t there. If you don’t need to use URI fragments, then don’t! Write a unit test to scan your JavaScript for mentions of window.location.hash, and have it fail if the pattern is found. When there is a need to use URI fragments, then you can discuss how to ensure their safe use.

Implement a Content-Security Policy

Browsers support Content-Security Policies that allow the author of a web-page to control where JavaScript (and other resources) can be loaded and executed from. XSS attacks rely on the attacker being able to run malicious scripts on a user’s web page - either by injecting inline <script> tags somewhere within the <html> tag of a page, or by tricking the browser into loading the JavaScript from a malicious third-party domain.

By setting a content security policy in the response header, you can tell the browser to never execute inline JavaScript, and to lock down which domains can host JavaScript for a page:

Content-Security-Policy: script-src 'self' https://apis.google.com
By listing the URIs from which scripts can be loaded, you are implicitly stating that inline JavaScript is not allowed.

The content security policy can also be set in a <meta> tag in the <head> element of the page:

<meta http-equiv="Content-Security-Policy" 
      content="script-src 'self' https://apis.google.com">

This approach will protect your users very effectively! However, it may take a considerable amount of discipline to make your site ready for such a header. Inline scripts tags are considered bad practice in modern web-development - mixing content and code makes web-applications difficult to maintain - but are common in older, legacy sites.

To migrate away from inline scripts incrementally, consider makings use of CSP Violation Reports. By adding a report-uri directive in your policy header, the browser will notify you of any policy violations, rather than preventing inline JavaScript from executing:

Content-Security-Policy-Report-Only: script-src 'self'; report-uri http://example.com/csr-reports

This will give you reassurance that there are no lingering inline scripts, before you ban them outright.

Further Reading