Why do React elements have $$ typeof property?

Original author: Dan Abramov
  • Transfer

About the React mechanism to prevent the possibility of JSON injection for XSS, and to avoid typical vulnerabilities.


You might think that you are writing JSX:


<marquee bgcolor="#ffa7c4">hi</marquee>

But in fact, you call the function:


React.createElement(
  /* type */'marquee',
  /* props */ { bgcolor: '#ffa7c4' },
  /* children */'hi'
)

And this function returns you a regular object called the React element. Accordingly, after traversing all the components, a tree of similar objects is obtained:


{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'),
}

If you previously used React, you may be familiar with the type, props, key, and ref fields. But what kind of property $$typeof? And why does he have a symbol as a value Symbol()?




Before the UI libraries became popular, to display client input in the application code, they generated a string containing HTML markup and inserted it directly into the DOM via innerHTML:


const messageEl = document.getElementById('message');
messageEl.innerHTML = '<p>' + message.text + '</p>';

Such a mechanism works fine, except when message.textit matters <img src onerror="stealYourPassword()">. Accordingly, we conclude that we do not need to interpret all client input as HTML markup.


To protect against such attacks, you can use secure APIs, such as document.createTextNode()or textContent, which do not interpret the text. And as an extra measure, screen strings, replacing potentially dangerous characters, such as <, >with safe ones.


However, the probability of an error is high, as it is difficult to keep track of all the places where you use the information recorded by the user in your page. That is why modern libraries such as React work safely with any default text:


<p>
  {message.text}
</p>

If message.textis a malicious string with a tag <img>, it will not turn into a real tag <img>. React escapes the text content and then adds it to the DOM. So instead of seeing the tag <img>, you just see its markup as a string.


To display arbitrary HTML inside React element, you must use the following structure: dangerouslySetInnerHTML={{ __html: message.text }}. The design is intentionally inconvenient. Due to its awkwardness, it becomes more visible, and attracts attention when viewing the code.




Does this mean that React is completely safe? Not. There are many ways to attack, based on the use of HTML and DOM. Tag attributes deserve special attention. For example, if you write <a href={user.website}>, it is possible to substitute a text link malicious code: 'javascript: stealYourPassword()'.


In most cases, the presence of vulnerabilities on the client side, are the result of problems with the server side, and above all should be fixed on it.


However, securely displaying custom text content is a sensible first line of defense that repels many potential attacks.


Based on the previous reasoning, it can be concluded that the following code should be completely safe:


// Автоматическое экранирование
<p>
  {message.text}
</p>

But this is also not the case. And here we are getting closer to explaining the presence $$typeofin the element React.




As we found out earlier, React elements are simple objects:


{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'),
}

Normally, a React element is created using a function call React.createElement(), but you can create it immediately with a literal, as I just did above.


Suppose that we store on the server a string that the user previously sent us, and each time we display it on the client side. But someone instead of the line sent us JSON:


let expectedTextButGotJSON = {
  type: 'div',
  props: {
    dangerouslySetInnerHTML: {
      __html: '/* здесь пишем вредоносный код */'
    },
  },
  // ...
};
let message = { text: expectedTextButGotJSON };
// Опасный момент для React 0.13
<p>
  {message.text}
</p>

That is, instead of the expected string, suddenly expectedTextButGotJSONJSON turned out to be a variable value . Which will be processed by React as a literal, and thereby executes malicious code.


React 0.13 is vulnerable to a similar XSS attack, but since version 0.14 each element is marked with a symbol:


{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'),
}

This protection works because the characters are not valid JSON values. Therefore, even if the server has a potential vulnerability and returns JSON instead of text, JSON cannot contain Symbol.for('response.element'). React checks an element for presence element.$$typeofand refuses to process the element if it is missing or invalid.


The main advantage Symbol.for()is that the characters are global between contexts because they use the global registry. Thus, they provide the same return value even in an iframe. And even if there are several copies of React on the page, they will still be able to “solgas” through a single value $$typeof.




And what about browsers that do not support symbols?


Alas, they will not be able to implement the additional protection discussed above, but the React elements will still contain a property $$typeoffor consistency, but it will be just a number - 0xeac7.


Why exactly 0xeac7? Because it looks like "React".


Also popular now: