XSS in React

Since React is a library for creating component based interfaces, most of the attacks surfaces in issues related to rendering elements in the DOM.

JSX Prevents Injection attacks

By default, React DOM escapes any values embedded in JSX before rendering them. Thus it ensures that you can never inject anything that’s not explicitly written in your application. Everything is converted to a string before being rendered. This helps prevent XSS (cross-site-scripting) attacks.

const title = response.potentiallyMaliciousInput;
const element = <h1>{title}</h1>;

According to documentation, Babel compiles JSX down to React.createElement() calls.

React.createElement(
  type,
  [props],
  [...children]
)

createElement creates and returns a new React element of the given type, where props contains a list of attributes passed to the new element and children contains the child node(s) of the new element (which, in turn, are more React components).

So above code may compile to

const element = React.createElement(
  'h1',
  {},
  '_escaped_title_'
);

Escaping code in React DOM works great when passing a string in [...children] as we did with _escaped_title_, but the other two arguments, type and props are passed unescaped.

This could potentially lead to XSS attacks if bad programming practices are used.

Bad Programming Patterns

  • Creating React components from user-supplied objects, i.e. setting the type attribute with data supplied by user.
  • Explicitly setting the dangerouslySetInnerHTML prop of an element;
  • Rendering links with user-supplied href attributes, or other HTML tags with injectable attributes (link tag, HMTL5 imports);
  • Passing user-supplied strings to eval().

Controlling Element Type

While creating a dynamic element with type provided by the user isn’t on its own harmful since it would only result in a plain attribute-less HTML Element, setting the properties of the newly created element would have dangerous effects.

Injecting Props

Say you have set up the system such that you parse user supplied JSON and then parse it to use the resulting object as props.

attacker_props = JSON.parse(stored_value)
React.createElement("span", attacker_props};

Here, if the attacker wishes they can use the following payload to set the dangerouslySetInnerHTML property. This property is React’s replacement for using innerHTML in the browser DOM.

{
    "dangerouslySetInnerHTML" : {
        "__html": "<img src=x/onerror=’alert(localStorage.access_token)’>"
    }
}

dangerouslySetInnerHTML

Avoid this property as much as you can. If need be thoroughly test your app and use preventive measures such as sanitizing both at server side and user side, and use whitelist methods.

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"))

Injectable Attributes

If the user controls the href attribute of a dynamically generated a tag then there is nothing to prevent the attacker from injecting a javascript: url.

<a href={userinput}>Link</a>
<button form="name" formaction={userinput}>

Eval-based Injection

If the attacker can provide an input that is then dynamically evaluated then there is nothing to stop them from injecting harmful code.

function antiPattern() {
  eval(this.state.attacker_supplied);
}

// Or even crazier
fn = new Function("..." + attacker_supplied + "...");
fn()