Warum haben React-Elemente eine Eigenschaft vom Typ $$?

Informationen zum Reaktionsmechanismus zur Verhinderung der Möglichkeit einer JSON-Injektion für XSS und zur Vermeidung häufiger Sicherheitslücken.


Sie könnten denken, Sie schreiben JSX:


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

Aber eigentlich rufen Sie die Funktion auf:


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

Und diese Funktion gibt Ihnen ein reguläres Objekt zurück, das als React-Element bezeichnet wird. Dementsprechend wird nach Durchlaufen aller Komponenten ein Baum ähnlicher Objekte erhalten:


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

Wenn Sie zuvor React verwendet haben, sind Sie möglicherweise mit den Feldern Typ, Requisiten, Schlüssel und Referenz vertraut. Aber was ist die Eigenschaft $$typeof ? Und warum hat es das Symbol Symbol() als Wert?




Bevor die UI-Bibliotheken populär wurden, wurde zur Anzeige von Client-Eingaben im Anwendungscode eine Zeile mit HTML-Markup generiert und über innerHTML direkt in das DOM eingefügt:


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

Dieser Mechanismus funktioniert message.text es sei denn, message.text ist auf <img src onerror="stealYourPassword()"> . Dementsprechend kommen wir zu dem Schluss, dass Sie nicht alle Client-Eingaben als HTML-Markup interpretieren müssen.


Zum Schutz vor solchen Angriffen können Sie sichere APIs wie document.createTextNode() oder textContent , die den Text nicht interpretieren. Und als zusätzliche Maßnahme entkommen Sie Zeichenfolgen, indem Sie potenziell gefährliche Zeichen wie < , > durch sichere ersetzen.


Trotzdem ist die Fehlerwahrscheinlichkeit hoch, da es schwierig ist, alle Stellen zu verfolgen, an denen Sie die vom Benutzer auf Ihrer Seite aufgezeichneten Informationen verwenden. Aus diesem Grund arbeiten moderne Bibliotheken wie React sicher mit jedem Standardtext:


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

Wenn message.text eine schädliche Zeichenfolge mit einem <img> , wird sie nicht zu einem echten <img> . React entgeht dem Textinhalt und fügt ihn dann dem DOM hinzu. Anstatt das <img> , sehen Sie daher einfach das Markup als Zeichenfolge.


Um beliebigen HTML- dangerouslySetInnerHTML={{ __html: message.text }} in einem React-Element anzuzeigen, müssen Sie die folgende Konstruktion verwenden: dangerouslySetInnerHTML={{ __html: message.text }} . Das Design ist absichtlich unangenehm. Aufgrund seiner Absurdität wird es auffälliger und fällt beim Betrachten des Codes auf.




Bedeutet dies, dass React völlig sicher ist? Nein. Es gibt viele bekannte Angriffsmethoden, die auf HTML und DOM basieren. Tag-Attribute verdienen besondere Aufmerksamkeit. Wenn Sie beispielsweise <a href={user.website}> schreiben, können Sie den Schadcode 'javascript: stealYourPassword()' als Textlink 'javascript: stealYourPassword()' .


In den meisten Fällen ist das Vorhandensein von Sicherheitslücken auf der Clientseite auf Probleme auf der Serverseite zurückzuführen und sollte zunächst behoben werden.


Die sichere Anzeige von benutzerdefiniertem Textinhalt ist jedoch eine vernünftige erste Verteidigungslinie, die viele potenzielle Angriffe widerspiegelt.


Aufgrund früherer Überlegungen können wir den Schluss ziehen, dass der folgende Code absolut sicher sein sollte:


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

Das ist aber auch nicht der Fall. Und hier kommen wir der Erklärung des Vorhandenseins von $$typeof im React-Element näher.




Wie bereits erläutert, sind React-Elemente einfache Objekte:


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

Normalerweise wird ein React-Element durch Aufrufen der Funktion React.createElement() Sie können es jedoch sofort mit einem Literal erstellen, wie oben beschrieben.


Angenommen, wir speichern auf dem Server eine Zeichenfolge, die der Benutzer zuvor an uns gesendet hat, und jedes Mal, wenn wir sie auf der Clientseite anzeigen. Aber jemand hat uns anstelle einer Zeichenfolge JSON geschickt:


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

Das heißt, anstelle der erwarteten Zeichenfolge stellte sich plötzlich heraus, dass der Wert der Variablen " expectedTextButGotJSON JSON war. Welches von React als Literal verarbeitet wird und dadurch bösartigen Code ausführt.


React 0.13 ist anfällig für einen XSS-ähnlichen Angriff. Ab Version 0.14 ist jedes Element mit einem Symbol gekennzeichnet:


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

Ein solcher Schutz funktioniert, weil Zeichen kein gültiger JSON-Wert sind. Selbst wenn der Server eine potenzielle Sicherheitsanfälligkeit aufweist und JSON anstelle von Text zurückgibt, kann JSON daher Symbol.for('response.element') nicht enthalten. React überprüft das Element auf element.$$typeof und weigert sich, das Element zu verarbeiten, wenn es fehlt oder ungültig ist.


Der Hauptvorteil von Symbol.for() besteht darin, dass Symbole zwischen Kontexten global sind, da sie eine globale Registrierung verwenden. Dies stellt den gleichen Rückgabewert auch in einem Iframe sicher. Und selbst wenn sich mehrere Kopien von React auf der Seite befinden, können sie durch einen einzigen Wert von $$typeof "übereinstimmen".




Was ist mit Browsern, die keine Zeichen unterstützen?


Leider können sie den oben beschriebenen zusätzlichen Schutz nicht implementieren, aber React-Elemente enthalten aus Konsistenzgründen weiterhin die Eigenschaft $$typeof , es handelt sich jedoch nur um eine Zahl - 0xeac7 .


Warum genau 0xeac7 ? Weil es aussieht wie Reagieren.

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


All Articles