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:
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.
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.
- Strip out all dirty HTML and returns clean HTML data npm Weekly download 50k+
- 40 contributors
- Earned 2800+ GitHub ⭐️
- 5.6kB MINIFIED + GZIPPED
- 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
- 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
- Donghyuk(Jacob) Jang / jacob.jang@jam3.com
- Iran Reyes / iran.reyes@jam3.com
- Peter Altamirano / peter.altamirano@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 jam3.com.
Article by Donghyuk (Jacob) Jang
Top comments (1)
Thanks for such clear article 👏