About the React mechanism for preventing JSON injection for XSS, and for avoiding 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 used React before, you may be familiar with the type, props, key and ref fields. But what is the $$typeof
property? And why does it have the Symbol()
symbol as its value?
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>';
This mechanism works fine, except when message.text
is set to <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, escape lines, replacing potentially dangerous characters such as <
, >
with safe ones.
However, the likelihood of an error is high, as it is difficult to keep track of all the places where you use user-recorded information in your page. That is why modern libraries such as React work safely with any default text:
<p> {message.text} </p>
If message.text
is a malicious string with an <img>
, it will not turn into a real <img>
. React escapes the text content and then adds it to the DOM. Therefore, instead of seeing the <img>
, you simply see its markup as a string.
To display arbitrary HTML inside a React element, you must use the following construction: 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}>
'javascript: stealYourPassword()'
, then you can substitute malicious code as a text link: 'javascript: stealYourPassword()'
.
In most cases, the presence of vulnerabilities on the client side, are the result of problems from the server side, and above all should be fixed on it.
However, the secure display of 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 of $$typeof
in the React element.
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 by calling the React.createElement()
function, 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 has previously sent us, and each time we display it on the client side. But instead of a string, someone sent us JSON:
let expectedTextButGotJSON = { type: 'div', props: { dangerouslySetInnerHTML: { __html: '/* */' }, }, // ... }; let message = { text: expectedTextButGotJSON }; // React 0.13 <p> {message.text} </p>
That is, suddenly, instead of the expected string, the value of the variable expectedTextButGotJSON
turned out to be JSON. 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 the presence of element.$$typeof
and refuses to process the element if it is missing or invalid.
The main advantage of Symbol.for()
is that the symbols 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 "say down" through the single value of $$typeof
.
What about browsers that don't support symbols?
Alas, they will not be able to implement the additional protection discussed above, but the React elements will still contain the $$typeof
property for consistency, but it will be just a number - 0xeac7
.
Why exactly 0xeac7
? Because it looks like "React".
Source: https://habr.com/ru/post/432350/
All Articles