Pourquoi les éléments React ont-ils une propriété $$ typeof?

À propos du mécanisme React pour empêcher la possibilité d'une injection JSON pour XSS et pour éviter les vulnérabilités courantes.


Vous pourriez penser que vous écrivez JSX:


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

Mais en fait, vous appelez la fonction:


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

Et cette fonction vous renvoie un objet régulier appelé élément React. En conséquence, après avoir traversé tous les composants, un arbre d'objets similaires est obtenu:


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

Si vous avez déjà utilisé React, vous connaissez peut-être les champs type, props, key et ref. Mais quelle est la propriété $$typeof ? Et pourquoi at-il le symbole Symbol() comme valeur?




Avant que les bibliothèques d'interface utilisateur ne deviennent populaires, pour afficher l'entrée client dans le code d'application, une ligne contenant le balisage HTML a été générée et insérée directement dans le DOM, via innerHTML:


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

Ce mécanisme fonctionne <img src onerror="stealYourPassword()"> sauf si message.text est défini sur <img src onerror="stealYourPassword()"> . Par conséquent, nous concluons que vous n'avez pas besoin d'interpréter toutes les entrées client comme du balisage HTML.


Pour vous protéger contre de telles attaques, vous pouvez utiliser des API sécurisées, telles que document.createTextNode() ou textContent , qui n'interprètent pas le texte. Et comme mesure supplémentaire, échappez aux chaînes en remplaçant les caractères potentiellement dangereux comme < , > par des caractères sûrs.


Néanmoins, la probabilité d'erreur est élevée, car il est difficile de suivre tous les endroits où vous utilisez les informations enregistrées par l'utilisateur dans votre page. C'est pourquoi les bibliothèques modernes telles que React fonctionnent en toute sécurité avec n'importe quel texte par défaut:


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

Si message.text est une chaîne malveillante avec une <img> , elle ne se transformera pas en une vraie <img> . React échappe au contenu texte, puis l'ajoute au DOM. Par conséquent, au lieu de voir la <img> , vous voyez simplement son balisage sous forme de chaîne.


Pour afficher un code HTML arbitraire à l'intérieur d'un élément React, vous devez utiliser la construction suivante: dangerouslySetInnerHTML={{ __html: message.text }} . Le design est intentionnellement inconfortable. En raison de son absurdité, il devient plus visible et attire l'attention lors de la visualisation du code.




Est-ce à dire que React est totalement sûr? Non. Il existe de nombreuses méthodes d'attaque connues basées sur HTML et DOM. Les attributs des balises méritent une attention particulière. Par exemple, si vous écrivez <a href={user.website}> , vous pouvez remplacer un code malveillant sous forme de lien texte: 'javascript: stealYourPassword()' .


Dans la plupart des cas, la présence de vulnérabilités côté client est le résultat de problèmes côté serveur et doit être corrigée en premier lieu.


Cependant, l'affichage sûr du contenu texte personnalisé est une première ligne de défense raisonnable qui reflète de nombreuses attaques potentielles.


Sur la base de considérations précédentes, nous pouvons conclure que le code suivant doit être complètement sûr:


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

Mais ce n'est pas le cas non plus. Et ici, nous nous rapprochons de l'explication de la présence de $$typeof dans l'élément React.




Comme nous l'avons expliqué précédemment, les éléments React sont des objets simples:


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

Habituellement, un élément React est créé en appelant la fonction React.createElement() , mais vous pouvez le créer immédiatement avec un littéral, comme je viens de le faire ci-dessus.


Supposons que nous stockons sur le serveur une chaîne que l'utilisateur nous a envoyée précédemment, et à chaque fois que nous l'afficherons côté client. Mais quelqu'un, au lieu d'une chaîne, nous a envoyé JSON:


 let expectedTextButGotJSON = { type: 'div', props: { dangerouslySetInnerHTML: { __html: '/*     */' }, }, // ... }; let message = { text: expectedTextButGotJSON }; //    React 0.13 <p> {message.text} </p> 

Autrement dit, au lieu de la chaîne attendue, la valeur de la variable attenduTextButGotJSON s'est avérée être JSON. Qui sera traité par React comme un littéral, et exécutera ainsi du code malveillant.


React 0.13 est vulnérable à une attaque de type XSS, mais à partir de la version 0.14, chaque élément est marqué d'un symbole:


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

Cette protection fonctionne car les caractères ne sont pas une valeur JSON valide. Par conséquent, même si le serveur présente une vulnérabilité potentielle et renvoie JSON au lieu de texte, JSON ne peut pas contenir Symbol.for('response.element') . React vérifie l'élément pour l' element.$$typeof et refuse de traiter l'élément s'il est manquant ou non valide.


Le principal avantage de Symbol.for() est que les symboles sont globaux entre les contextes car ils utilisent un registre global. Cela garantit la même valeur de retour même dans un iframe. Et même s'il y a plusieurs exemplaires de React sur la page, ils pourront toujours «correspondre» à travers une seule valeur de $$typeof .




Qu'en est-il des navigateurs qui ne prennent pas en charge les caractères?


Hélas, ils ne pourront pas implémenter la protection supplémentaire discutée ci-dessus, mais les éléments React contiendront toujours la propriété $$typeof pour la cohérence, mais ce ne sera qu'un nombre - 0xeac7 .


Pourquoi exactement 0xeac7 ? Parce que ça ressemble à React.

Source: https://habr.com/ru/post/fr432350/


All Articles