Overreacted

なぜReact Elementは$$typeofプロパティを持っているの?

2018 M12 3 • ☕️ 3 min read

あなたはJSXを書いていると思うかもしれません:

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

しかし実際には関数を呼び出しています。:

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

そしてこの関数はオブジェクトを返します。このオブジェクトをReact element と呼びます。次に何をレンダリングするかReactに指示します。あなたの書いたコンポーネントはそれらのツリーを返します。

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

Reactを使ったことがあればtype, props, key,refフィールドは知っているかもしれませんが、 $$typeofってなんだ?しかもなぜ値にSymbol()が入っているんだ?

これはReactを使うことにおいては知る必要がないことですが、この記事にはセキュリティについてのいくつかのヒントがあります。 いつかあなたはUIライブラリを書くでしょう、その時これは役に立つかもしれません。 私はそう望んでいます。


クライアントサイドのUIライブラリが一般的になる前はアプリケーションコードにHTMLを構築してDOMに挿入するのが一般的でした。:

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

message.text'<img src onerror = "stealYourPassword()">'のようなものである場合を除いて、それは問題なく動作します。 見知らぬ人によって書かれたものが、アプリケーションのレンダリングされたHTMLにそのまま表示されることを望まないでください。

(面白い事実:クライアントサイドでのレンダリングだけを行うのであれば、<script>タグを使ってもJavaScriptを実行できません。)

そのような攻撃から保護するために、テキストだけを扱う document.createTextNode()textContentのような安全なAPIを使うことができます。 また、ユーザーが指定したテキスト内の <>などの潜在的に危険な文字を置き換えることによって、入力を率先してにエスケープすることもできます。

それでも、間違いのコストは高く、ユーザーが作成した文字列を出力に挿入するたびに保護しなければいけないのは面倒です。 これが、Reactのような最新のライブラリがデフォルトで文字列のテキストコンテンツをエスケープする理由です。 :

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

message.text<img>や他のタグを含む悪意のある文字列である場合、それは本当の<img>タグにはなりません。 ReactはコンテンツをエスケープしてDOMに挿入します。 そのため <img>タグを見る代わりに、そのマークアップを見るだけです。

React elements内に任意のHTMLを描画するには、 dangerouslySetInnerHTML = {{__html:message.text}}と書く必要があります。 ぎこちない感じに書くというのが特徴です コードレビューやコードベース監査で確認できるように、見やすくすることを目的としています。


Reactがインジェクション攻撃から完全に安全であるということですか? 違います。 HTMLとDOMはたくさんの攻撃対象領域を提供しますが、Reactや他のUIライブラリがそれを軽減するには難しすぎるか遅すぎます。他の攻撃手法の大部分は属性を含みます。 たとえば、 <a href={user.website}>をレンダリングする場合は、Webサイトが 'javascript:stealYourPassword()'であるユーザーに注意してください。 <div {... userData}>のようにユーザの情報を展開することはまれですが危険です。

Reactは時間をかけてより多くの保護を提供することができますが、多くの場合、これらは修正されるべきサーバーの問題の結果です。

それでも、テキストコンテンツのエスケープは、多くの潜在的な攻撃をキャッチする合理的な防御の第一線です。 このようなコードが安全であることを知っておくのはいいことではありませんか?

// 自動でエスケープされます
<p>
  {message.text}
</p>

まあ、必ずしも安全ではないですが。 そこで $$typeofが登場します。


React elementsは、設計上、単純なオブジェクトです。

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

通常、それらを React.createElement()で作成しますが、必須ではありません。上で行ったように書かれたオブジェクトをサポートするReactための有効な使用例があります。もちろん、このように記述したくないと思うかもしれません - しかし、これは最適化コンパイラ、ワーカー間でのUI要素の受け渡し、またはReactパッケージからのJSXの分離に役立ちます。

しかしながら、サーバーに任意のJSONオブジェクトを保存するための口がある場合 クライアントコードは文字列を期待します、これは問題になるかもしれません:

// サーバーにユーザー情報のJSONを許可する口がある場合
let expectedTextButGotJSON = {  type: 'div',  props: {    dangerouslySetInnerHTML: {      __html: '/* ここに悪いコードを置く */'    },  },  // ...};let message = { text: expectedTextButGotJSON };

// React 0.13で危険
<p>
  {message.text}</p>

その場合、React 0.13はXSS攻撃に対して脆弱になります。 さらに明確にすると、この攻撃は既存のサーバーホールに依存しています。 それでも、Reactはその問題に対して人々を保護するためのより良い仕事をすることができました。 React 0.14から始まっています。

React 0.14での修正は、すべてのReact要素にシンボルを付けるです。:

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

JSONには単にSymbolを入れることができないので、これはうまくいきます。 そのため、サーバーにセキュリティホールがあり、テキストではなくJSONを返す場合でも、そのJSONには Symbol.for( 'react.element')を含めることができません。 Reactはelement.$$typeofをチェックします。そしてそれがない場合もしくは無効な場合に要素の処理を拒否します。

特にSymbol.for()を使うことのいいところは、シンボルはiframeやワーカーのような環境間でグローバルであるということです。 そのため、この修正によって、よりエキゾチックな状況でも、アプリのさまざまな部分の間で信頼できる要素を渡すことが妨げられることはありません。 同様に、たとえページ上にReactのコピーが複数あっても、それらは有効な $$typeofの値に「同意する」ことができます。


Symbolsをサポートしていないブラウザーについてはどうですか?

ああ、彼らはこの特別な保護を受けていません。Reactは一貫性のためにまだ要素上に $$typeofフィールドを含んでいますが、それは数値に設定されています — 0xeac7

なぜこの数字なの? 0xeac7はちょっと“React”のように見えますねぇ。