191

I read this on the React tutorial. What does this mean?

React is safe. We are not generating HTML strings so XSS protection is the default.

How do XSS attacks work if React is safe? How is this safety achieved?

Anders
  • 8,307
  • 9
  • 56
  • 88
user1210233
  • 2,730
  • 5
  • 24
  • 31

2 Answers2

387

ReactJS is quite safe by design since

  1. String variables in views are escaped automatically
  2. With JSX you pass a function as the event handler, rather than a string that can contain malicious code

so a typical attack like this will not work

const username = "<img onerror='alert(\"Hacked!\")' src='invalid-image' />";

class UserProfilePage extends React.Component {
  render() {
    return (
      <h1> Hello {username}!</h1>
    );
  }
}

ReactDOM.render(<UserProfilePage />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

but ...

❗❗❗Warning❗❗❗

There are still some XSS attack vectors that you need to handle yourself in React!

1. XSS via dangerouslySetInnerHTML

When you use dangerouslySetInnerHTML you need to make sure the content doesn't contain any javascript. React can't do here anything for you.

const aboutUserText = "<img onerror='alert(\"Hacked!\");' src='invalid-image' />";

class AboutUserComponent extends React.Component {
  render() {
    return (
      <div dangerouslySetInnerHTML={{"__html": aboutUserText}} />
    );
  }
}

ReactDOM.render(<AboutUserComponent />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

2. XSS via a.href attribute

Example 1: Using javascript:code

Click on "Run code snippet" -> "My Website" to see the result

const userWebsite = "javascript:alert('Hacked!');";

class UserProfilePage extends React.Component {
  render() {
    return (
      <a href={userWebsite}>My Website</a>
    )
  }
}

ReactDOM.render(<UserProfilePage />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Example 2: Using base64 encoded data:

Click on "Run code snippet" -> "My Website" to see the result

const userWebsite = "data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGFja2VkISIpOzwvc2NyaXB0Pg==";

class UserProfilePage extends React.Component {
  render() {
    const url = userWebsite.replace(/^(javascript\:)/, "");
    return (
      <a href={url}>My Website</a>
    )
  }
}

ReactDOM.render(<UserProfilePage />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

3. XSS via attacker controlled props

const customPropsControledByAttacker = {
  dangerouslySetInnerHTML: {
    "__html": "<img onerror='alert(\"Hacked!\");' src='invalid-image' />"
  }
};

class Divider extends React.Component {
  render() {
    return (
      <div {...customPropsControledByAttacker} />
    );
  }
}

ReactDOM.render(<Divider />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Here are more resources

Marty Aghajanyan
  • 12,651
  • 8
  • 35
  • 37
  • 48
    This answer is amazing! With the code snippets and the references at the end...! Thank you! – Ioanna Apr 10 '19 at 03:11
  • Is any of the examples above has been taken care of by React since this answer was written? I'm asking, since I read in the following slideshre: https://www.slideshare.net/kseniadmitrieva/how-to-react-to-javascript-insecurity slide #20 that user-controlled props where fixed in React 0.14 in November 15' – omer Jul 01 '19 at 15:47
  • @omer no, and react decided to not take care of this attack vectors in React level. Here are few good comments, explaining why they are not handling in React level https://github.com/facebook/react/issues/3473 (https://github.com/facebook/react/issues/3473#issuecomment-91349525, https://github.com/facebook/react/issues/3473#issuecomment-90594748) – Marty Aghajanyan Jul 02 '19 at 04:22
  • 1
    @omer the issue that you are referring was a security bug and it fixed, but the point 3 that I have listed is not related to that one, you still can check out that 3rd point work by executing my code under any react version. – Marty Aghajanyan Aug 02 '19 at 18:47
  • if you could also address how to prevent these vulnerabilities – Rahul Yadav Jun 24 '21 at 20:00
  • 1
    @RahulYadav use dompurify package to sanitize the data. Here's the link https://www.npmjs.com/package/dompurify – anji5h Jun 27 '21 at 13:11
  • Phenomenal comment! This should definitely be the top – Tim Kelly Dec 13 '21 at 05:40
  • As of June '22 none of the major browsers allow base64 nav in href, so 3b is effectively not an issue any longer. – sirclesam Jun 09 '22 at 17:08
81

React automatically escapes variables for you... It prevents XSS injection via string HTML with malicious Javascript.. Naturally, inputs are sanitized along with this.

For instance let's say you have this string

var htmlString = '<img src="javascript:alert('XSS!')" />';

if you try to render this string in react

render() {
    return (
        <div>{htmlString}</div>
    );
}

you will literally see on the page the whole string including the <span> element tag. aka in the browser you will see <img src="javascript:alert('XSS!')" />

if you view the source html you would see

<span>"<img src="javascript:alert('XSS!')" />"</span>

Here is some more detail on what an XSS attack is

React basically makes it so you can't insert markup unless you create the elements yourself in the render function... that being said they do have a function that allows such rendering its called dangerouslySetInnerHTML... here is some more detail about it


Edit:

Few things to note, there are ways to get around what React escapes. One more common way is when users define props to your component. Dont extend any data from user input as props!

Community
  • 1
  • 1
John Ruddell
  • 25,283
  • 6
  • 57
  • 86
  • 20
    Escapes everything? Really? React is NOT safe by default, there is a lot of things you have to do manually and attack vectors that you have to understand. All React does is escape html to string when you are trying to insert it with {html}. But there are a million other ways to allow XSS, which React does NOT protect against. , , – andree Oct 25 '18 at 08:40
  • 1
    @andree thanks for pointing my typo out. It is a 3 year old post. Obviously there are ways to get around what React escapes and each dev should be weary of that. – John Ruddell Oct 26 '18 at 22:06
  • Thanks for editing your answer @John Ruddell. No offense, but your answer made React look more secure than it actually is, and since your answer is one of the first that comes up on the topic, I just wanted to point that out. Unfortunately this is a common theme I see in overall frontend (not just React) security - things look secure or easily securable on the surface, but when you dig in, turns out there are big holes. Basic security questions should have answers that are easy to find that are summarized somewhere, unfortunately that is not my experience lately. – andree Oct 27 '18 at 10:52
  • 3
    Well.. over time documentation comes out as security is tested. Answers that we're once helpful aren't as helpful. Hard part is keeping all answers up to date with the changing tech – John Ruddell Oct 27 '18 at 16:24