XSS prevention - ReactJS security best practices
In software development, it is quite common to focus on back-end and database security rather than focusing on front-end security. In MachineMax, security is the number one priority, managing customer data is a fundamental point for the entire ecosystem and this includes the frontend. In this blog we would like to list some key points to check when developing a frontend application focusing on development with React.
Default XSS protection with data binding
A Cross-Site Scripting (XSS) vulnerability can and will lead to the full compromise of a frontend application. An XSS vulnerability allows the attacker to control the application in the user's browser, extract sensitive information, and make requests on behalf of the application. Modern frameworks come with built-in defences against XSS like React.
An XSS vulnerability is an injection vulnerability, where the attacker inserts a piece of malicious data into a web application. The maliciously injected data will be picked up by the browser, which interprets the data as code. The result is that the injected payload from the attacker will be executed as legitimate application code, giving the attacker full control over the application running in the user’s browser.
Use default data binding with curly braces {} and React will automatically escape values to protect against XSS attacks. Note that this protection only occurs when rendering textContent and not when rendering HTML attributes.
Use JSX data binding syntax {} to place data in your elements.
Do this:
Avoid dynamic attribute values without custom validation.
Don’t do this:
Dangerous URLs
URLs can contain dynamic script content via javascript: protocol URLs. Use validation to assure your links are http: or https: to avoid javascript: URL based script injection. Achieve URL validation using a native URL parsing function then match the parsed protocol property to an allow list.
Do this:
Don’t do this:
Rendering HTML
It is possible to insert HTML directly into rendered DOM nodes using dangerouslySetInnerHTML. Any content inserted this way must be sanitised beforehand.
Use a sanitisation library like dompurify or write your own on any values before placing them into the dangerouslySetInnerHTML prop.
Use dompurify when inserting HTML into the DOM:
Direct DOM Access
Accessing the DOM to inject content into DOM nodes directly should be avoided. Use dangerouslySetInnerHTML if you must inject HTML and sanitise it before injecting it using dompurify. Use the XSS protection data binding wrapping the code to a react component.
Do this:
Avoid using refs and findDomNode() to access rendered DOM elements to directly inject content via innerHTML and similar properties or methods.
Don’t do this:
Server-side rendering
Data binding will provide automatic content escaping when using server-side rendering functions like ReactDOMServer.renderToString() and ReactDOMServer.renderToStaticMarkup().
Avoid concatenating strings onto the output of renderToStaticMarkup() before sending the strings to the client for hydration.
Don’t concatenate unsanitised data with the output of renderToStaticMarkup() to avoid XSS:
Known vulnerabilities in dependencies
Some versions of third-party components might contain JavaScript security issues. Check your dependencies and update when better versions become available.
Use a tool like the free Snyk CLI to check for vulnerabilities.
Automatically fix vulnerabilities with Snyk by integrating with your source code management system to receive automated fixes:
Github security scanner
At MachineMax we use GitHub to store our repositories in a private environment. We use GitHub scanner to scan for any vulnerability that can come out from an outdated library or from a team of experts that analyse the security issues and release a fix as soon is deployed. Code scanning is a feature that you can use to analyse the code in a GitHub repository to find security vulnerabilities and coding errors.
You can use code scanning to find, triage, and prioritise fixes for existing problems in your code. Code scanning also prevents developers from introducing new problems. You can schedule scans for specific days and times, or trigger scans when a specific event occurs in the repository, such as a push.
If code scanning finds a potential vulnerability or error in your code, GitHub displays an alert in the repository. After you fix the code that triggered the alert, GitHub closes the alert.
In our configuration as soon a vulnerability is exploited GitHub will prepare a new PR with the fix that is checked by our engineering team and released as soon as possible.
JSON state
It is common to send JSON data along with server-side rendered React pages. Always escape < characters with a benign value to avoid injection attacks.
Do escape HTML significant values from JSON with benign equivalent characters:
Vulnerable versions of React
The React library has had a few high severity vulnerabilities in the past, so it is a good idea to stay up-to-date with the latest version.
Avoid vulnerable versions of the react and react-dom by verifying that you are on the latest version using npm outdated to see the latest versions.
Linters
Install Linter configurations and plugins that will automatically detect security issues in your code and offer remediation advice.
Use the ESLint React security config to detect security issues in our code base.
Configure a pre-commit hook that fails when security-related Linter issues are detected using a library like husky.
Use GitHub security scanner to automatically update to new versions when vulnerabilities exist in the versions you are using.
Dangerous library code
Library code is often used to perform dangerous operations like directly inserting HTML into the DOM. Review library code manually or with linters to detect unsafe usage of React’s security mechanisms.
Avoid libraries that do use dangerouslySetInnerHTML, innerHTML, unvalidated URLs or other unsafe patterns. Use security Linters on your node_modules folder to detect unsafe patterns in your library code.