XSS attacks in React
XSS = Cross-site scripting.
- A web application allows user input that is not validated or encoded.
- Malicious code (script) is injected into the web application.
- The malicious script can then be delivered to other users.
- The malicious script can access cookies, session tokens and other sensitive information retained by the browser.
- The private data can be transmitted back to the attacker.
- An attacker can do terrible things with said data.
Types of XSS attacks:
Stored XXS (Persistent):
- Malicious user input is sent to a trusted target server (eg. message board, comment, forum).
- The malicious script is permanently stored on the server.
- Innocent users access the trusted website with hidden malicious content.
- The nasty script can then be permanently stored in every innocent visitors browser.
Reflected XSS (Non-Persistent):
- A malicious script is injected into a vulnerable site.
- An attacker creates a malicious link and fishes for users to click it (email, comments, social media).
- After clicking the user is sent the exploited site with injected malicious scripts
- The malicious script is 'reflected' back to the innocent users browser
DOM Based XSS:
- Malicious modifications of the DOM environment on the victim's browser.
- The attackers code is not sent from the servers HTTP response object.
- It is in the client side script at runtime.
Solutions in JavaScript:
- avoid use
innerHTML
- if you do need to use
innerHTML
sanitize[1] it - do use
textContent
Solutions in React:
React takes care of a lot of client side security risks.
Better safe than sorry:
- Avoid using dangerouslySetInnerHTML.
- Unit Test your components
ALWAYS SANITIZE the following:
- dangerouslySetInnerHTML
- a.href attribute
- users input from forms
Sanitizing libraries
- DOMPurify - sanitizes HTML and prevents XSS attacks
- xss - filter input from users to prevent XSS attacks.
- xss-filters - Just sufficient output filtering to prevent XSS!
Ron Perris suggests whitelist validation for data passed as props with the url-parse npm package:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import URL from 'url-parse';
class SafeURL extends Component {
isSafe(dangerousURL, text) {
const url = URL(dangerousURL, {});
if (url.protocol === 'javascript:') return false;
if (url.protocol === '') return false;
return true;
}
render() {
const dangerousURL = this.props.dangerousURL;
const safeURL = this.isSafe(dangerousURL) ? dangerousURL : null;
return <a href={safeURL}>{this.props.text}</a>;
}
}
ReactDOM.render(
<SafeURL dangerousURL=' javascript: alert(1)' text='Click me!' />,
document.getElementById('root')
);
- Carlos Santana suggests a REGEX script to escape all
<script>
tags and events from tags.
const removeXSSAttacks = html => {
const SCRIPT_REGEX = /<script[^<]*(?:(?!</script>)<[^<]*)*</script>/gi;
// Removing the <script> tags
while (SCRIPT_REGEX.test(html)) {
html = html.replace(SCRIPT_REGEX, '');
}
// Removing all events from tags...
html = html.replace(/ onw+="[^"]*"/g, '');
return html;
};
- Carlos Santana suggests the serialize-javascript npm package to stringify instead of JSON.stringify.
- Chris Ferdinandi suggests a sanitizeHTML() function to remove unwanted characters.
/*!
* Sanitize and encode all HTML in a user-submitted string
* (c) 2018 Chris Ferdinandi, MIT License, https://gomakethings.com
* @param {String} str The user-submitted string
* @return {String} str The sanitized string
*/
var sanitizeHTML = function(str) {
var temp = document.createElement('div');
temp.textContent = str;
return temp.innerHTML;
};
For full details on preventing XSS attacks see the OWASP cheatsheet
[1] Sanitize
- remove disallowed markup.
<script>
,<object>,
<embed>
, and<link>
are removed by the sanitization process.- also dangerous attributes like onclick.