How to prevent XSS attacks when using dangerouslySetInnerHTML in React

This article intends to show one of the techniques we use to mitigate cross-site scripting (XSS) attacks at Jam3. These vulnerabilities may appear when dangerouslySetInnerHTML is wrongly used, and our goal is to detect it ahead of time and to clean up untrusted values.

dangerouslySetInnerHTML is one of the features commonly used for presenting and inserting DOM formatted content data into the frontend. It provides a number of benefits when parsing HTML strings into React’s virtual DOM. However, you may want to think twice about using it for user input. You must consider its vulnerabilities in order to prevent XSS attack.

XSS attacks

Cross-site scripting (XSS) allows attackers(hackers) to inject malicious code into a website for other end-users. By doing this, attackers may have access to personal data, cookies, webcams, and even more. Read more about Cross-site scripting.

Copy below string and paste into the input field.

Preventing XSS

This issue is not restricted to React; to learn how to prevent it in your web development OWASP has a good prevention cheat sheet. One approach to prevent XSS attacks is to sanitize data. It can be done either on the server-side or the client-side; in this article, we will focus on the client-side solution.

Preventing XSS with dangerouslyInnerSetHTML

Sanitizing content in the frontend when using dangerouslySetInnerHTML is always a good security practice, even with a trusted source of truth. For example, another development team in charge of maintaining the project changes the source of truth without realizing how it could impact the site. A change like that may cause a critical XSS vulnerability.

At Jam3 we avoid using dangerouslySetInnerHTML whenever possible. When it’s required, we always apply sanitization security layers on both the back-end and front-end. In addition, we created an ESLint rule called no-sanitizer-with-danger inside eslint-plugin-jam3 to detect improper use of dangerouslySetInnerHTML.

ESLint rule

I assume that you are already familiar with ESLint. If not, Get Started.

Installation

You’ll need to install eslint and eslint-plugin-jam3:

Extend pluginsin the .eslintrc config file by adding jam3. You can omit the eslint-plugin- prefix. Then, configure the rules by adding jam3/no-sanitizer-with-danger to the rules. Note: error level 2 is recommended. With this option, exit code will be 1. error level 1 will give warning alert, but does not affect exit code. 0 means to turn the rule off. The plugin will check that the content passed to dangerouslySetInnerHTML is wrapped in this sanitizer function. The wrapper function name can be also be changed in the JSON file (sanitizer is the default wrapper name).

How to use it

Here is an unsafe way of using dangerouslySetInnerHTML.

Once the rule is enabled, your code editor will alert the lack of a sanitizer in dangerouslySetInnerHTML. For the purpose of this article we use dompurify, you can find an extended list of available sanitizers at the end of the article.

The sanitizer wrapper must have a name, for the purpose of this article we are creating const sanitizer = dompurify.sanitize;. However, it is recommended to create a sanitization utility to abstract your chosen sanitizer.

Sanitizer libraries

Our team has researched and tried many sanitizers and can recommend these 3 libraries.

dompurify

  • Strip out all dirty HTML and returns clean HTML data
  • npm Weekly download 50k+
  • 40 contributors
  • Earned 2800+ GitHub ⭐️
  • 5.6kB MINIFIED + GZIPPED

xss

  • Escape HTML entity characters to prevent the attack which occurs to transform non-readable content for the end users
  • npm weekly download 30k+
  • 18 contributors
  • 5.3kB MINIFIED + GZIPPED
  • Earned 2500+github ⭐️

xss-filters

  • Escape HTML entity characters to prevent the attack which occurs to transform non-readable content for the end users
  • npm weekly download 30k+
  • 5 contributors
  • 2.1kB MINIFIED + GZIPPED
  • Earned 900+github ⭐️

Conclusion

To sum up, finding the most suitable sanitizer library for your project is very important for security. You might want to have a look at GitHub stars, npm download numbers, and maintenance routines. The use ofno-sanitizer-with-danger in eslint-plugin-jam3 will be a great choice to help ensure all data is being properly purified and gain confidence that your project will be safe from XSS vulnerabilities.

NOTE: Please keep it in mind there is a performance disadvantage of sanitizing data in client-side. For example, sanitizing all data at once may slow down the initial load. To prevent this in large scale web applications, you can implement a “lazy-sanitizing” approach to sanitize on the fly.

Further reading and sources

Contributors

Jam3 is a design and experience agency that partners with forward-thinking brands from around the world. To learn more, visit us at jam3.com.

Jam3 is a design and experience agency that partners with forward-thinking brands from around the world. To learn more, visit us at www.jam3.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store