loading...
Jam3

How to prevent XSS attacks when using dangerouslySetInnerHTML in React

donghyukjacobjang profile image Donghyuk (Jacob) Jang Updated on ・4 min read

XSS hero image

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.

Dangerously Set innerHTML

This feature is designed to present and insert DOM formatted content data into the frontend. The use of the feature is a bad practice, especially when dealing with user inputs and dynamic data. You must consider its vulnerabilities in order to prevent XSS attack.

"Easy" to make thing safe is one of React philosophy. React is flexible and extendable which means that the bad practice can be turning into the best practice. Sanitizing props value is one obvious option and strongly recommended.

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 https://placeimgxxx.com/320/320/any" onerror="alert('xss injection') and paste it in the input field in the xss injection example below:
Edit k9vxk9ppyo

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.

$ npm i eslint eslint-plugin-jam3 -save-dev

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.

dangerouslySetInnerHTML without sanitizer

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
  • Earned 2500+ github ⭐️
  • 5.3kB MINIFIED + GZIPPED

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
  • Earned 900+ github ⭐️
  • 2.1kB MINIFIED + GZIPPED

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 of no-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

  • ESLint developer guide
  • Creating an ESLint Plugin
  • eslint-plugin-react
  • eslint-plugin-jam3
  • Cross-site scripting
  • XSS attack cheat sheet

Contributors

Jam3 logo

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.

Article by Donghyuk (Jacob) Jang

Discussion

pic
Editor guide
Collapse
panczarny profile image
Michał Czarnota

Thanks for such clear article 👏