XSSのJSONインジェクションの可能性を防ぐためのReactメカニズムと、一般的な脆弱性の回避について。
あなたはJSXを書いていると思うかもしれません:
<marquee bgcolor="#ffa7c4">hi</marquee>
しかし、実際にはあなたは関数を呼び出しています:
React.createElement( /* type */ 'marquee', /* props */ { bgcolor: '#ffa7c4' }, /* children */ 'hi' )
この関数は、React要素と呼ばれる通常のオブジェクトを返します。 したがって、すべてのコンポーネントを走査した後、同様のオブジェクトのツリーが取得されます。
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
以前にReactを使用したことがある場合は、type、props、key、およびrefフィールドに精通している可能性があります。 しかし、 $$typeof
プロパティとは何ですか? そして、なぜその値としてシンボルSymbol()
ですか?
UIライブラリが普及する前に、アプリケーションコードでクライアント入力を表示するために、HTMLマークアップを含む行が生成され、innerHTMLを介してDOMに直接挿入されました。
const messageEl = document.getElementById('message'); messageEl.innerHTML = '<p>' + message.text + '</p>';
message.text
が<img src onerror="stealYourPassword()">
設定されていない限り、このメカニズムは<img src onerror="stealYourPassword()">
ます。 したがって、 すべてのクライアント入力をHTMLマークアップとして解釈する必要はないと結論付けています。
このような攻撃から保護するために、 document.createTextNode()
やtextContent
など、テキストを解釈しない安全なAPIを使用できます。 また、追加の手段として、 <
、 >
などの潜在的に危険な文字を安全な文字に置き換えて文字列をエスケープします。
それでも、ページでユーザーが記録した情報を使用するすべての場所を追跡することは難しいため、エラーの可能性は高くなります。 これが、Reactなどの最新のライブラリがデフォルトテキストで安全に動作する理由です。
<p> {message.text} </p>
message.text
が<img>
持つ悪意のある文字列である場合、実際の<img>
にはなりません。 Reactはテキストコンテンツをエスケープし、DOMに追加します。 したがって、 <img>
を表示する代わりに、そのマークアップを文字列として表示するだけです。
React要素内に任意のHTMLを表示するには、次の構造を使用する必要があります: dangerouslySetInnerHTML={{ __html: message.text }}
。 デザインは意図的に不快です。 その不条理により、コードは目立つようになり、コードを見るときに注目を集めます。
これは、Reactが完全に安全であることを意味しますか? いや HTMLとDOMに基づく多くの既知の攻撃方法があります。 タグ属性には特別な注意が必要です。 たとえば、 <a href={user.website}>
を記述した場合、テキストリンクとして悪意のあるコード'javascript: stealYourPassword()'
を置き換えることができます。
ほとんどの場合、クライアント側の脆弱性の存在はサーバー側の問題の結果であり、まず最初に問題を修正する必要があります。
ただし、カスタムテキストコンテンツの安全な表示は、多くの潜在的な攻撃を反映する合理的な最前線の防衛線です。
以前の考慮事項に基づいて、次のコードは完全に安全であると結論付けることができます。
// <p> {message.text} </p>
しかし、そうではありません。 そして、ここで、React要素内の$$typeof
の存在の説明に近づきます。
前に説明したように、React要素は単純なオブジェクトです。
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
通常、React要素はReact.createElement()
関数を呼び出すことによって作成されますが、上記で行ったように、リテラルを使用してすぐに作成できます。
ユーザーが以前に送信した文字列をサーバーに保存し、クライアント側で表示するたびに保存するとします。 しかし、文字列ではなく誰かがJSONを送信してくれました。
let expectedTextButGotJSON = { type: 'div', props: { dangerouslySetInnerHTML: { __html: '/* */' }, }, // ... }; let message = { text: expectedTextButGotJSON }; // React 0.13 <p> {message.text} </p>
つまり、予期される文字列の代わりに、 expectedTextButGotJSON
変数の値が突然JSONになりました。 Reactによってリテラルとして処理され、悪意のあるコードを実行します。
React 0.13はXSSのような攻撃に対して脆弱ですが、バージョン0.14以降、各要素には記号が付けられています。
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
文字は有効なJSON値ではないため、この保護は機能します。 したがって、サーバーに潜在的な脆弱性があり、テキストではなくJSONを返す場合でも、JSONにSymbol.for('response.element')
含めることはできません。 Reactは要素のelement.$$typeof
をチェックし、要素が見つからないか無効な場合、要素の処理を拒否します。
Symbol.for()
の主な利点は、シンボルがグローバルレジストリを使用するため、コンテキスト間でシンボルがグローバルであることです。 これにより、iframeでも同じ戻り値が保証されます。 また、ページ上にReactのコピーが複数ある場合でも、 $$typeof
単一の値を通じて「一致」することができます。
文字をサポートしていないブラウザはどうですか?
残念ながら、上記で説明した追加の保護を実装することはできませんが、React要素には一貫性を0xeac7
ために$$typeof
プロパティが含まれていますが、これは単なる数字0xeac7
です。
なぜ正確に0xeac7
か? Reactのように見えるからです。