loading...

Preventing XSS in React Applications

spukas profile image Linas Spukas ・2 min read

Cross-site scripting (XSS) accounts for the majority of web applications security vulnerabilities. It lets attackers inject client-side scripts into web pages, bypass access controls, steal sessions, cookies, connect to ports or computers camera.

JSX Data Binding

To fight against XSS, React prevents render of any embedded value in JSX by escaping anything that is not explicitly written in the application. And before rendering it converts everything to a string.

A good example of how React escapes embedded data if you try to render the following content:

function App() {
  const userInput = "Hi, <img src='' onerror='alert(0)' />";

  return (
    <div>
      {userInput}
    </div>
  );
}

The output in the browser will be: Hi, , rendered as a string with an image tag being escaped. That is very handy and covers simple cases where an attacker could inject the script. If you would try to load the same content directly in the DOM, you would see an alert message popped out.

3rd Party Libraries

With the more complex cases, where it is not enough just to render data through JSX, the risk is much higher. For example, the business requirements changed, and application now needs to accept user input with embedded data like styling bold, italic and etc. tags. Let's say we need to render user input: <b>Hi React</b> with the desired output: Hi React.
This requires a direct injection into the DOM for data to be parsed as HTML, and it can be done by setting dangerouslySetInnerHTML:

const userInput = <b>Hi React</b>;
return <div dangerouslySetInnerHTML={{ __html: userInput }} />;

This will solve business requirements that let user directly style the text, but at the same time it opens a huge risk and possibility for XSS atacks. Now if the evil user enters <b>"Hi, <img src='' onerror='alert(0)' />"</b> the browser will render this:

Alt Text

To avoid running dangerous scripts they should be sanitized before rendering. The best option is to use a 3rd party library, for example, popular and maintained library dompurify with zero dependencies sanitizes HTML. Improved code would now:

import createDOMPurify from "dompurify";
const DOMPurify = createDOMPurify(window);

function App() {
  const userInput = "<b>Hi, <img src='' onerror='alert(0)' /></b>";

  return (
    <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />
  );
}

The render content will look not: Hi, and an empty image tag will be escaped.

But do not rely on all parsing libraries. For example, using a popular react-html-parser at first will seem that it does the job, escapes the script from the image tag, but it would parse the following code:

return (
  <div>
    {ReactHtmlParser('<iframe src="javascript:alert(0)"></iframe>')}
  </div>;

Which will result in running the script in iframe and invoking alert modal. Parsers do not scape all scripts, especially in iframes.

Summing Up

Simple tips to follow for securing React application and minimizing threat for XSS attacks:

  • Render data in JSX when possible to use implemented securities by React
  • Sanitize data when using dangerouslySetInnerHTML
  • Do not trust HTML parsers to escape vulnerable scripts
  • Avoid direct data injection into the DOM

Posted on Mar 22 by:

spukas profile

Linas Spukas

@spukas

Full-stack web developer with a specialisation in React and NodeJS.

Discussion

markdown guide