Reactの「自動エスケープ」を過信するな。dangerouslySetInnerHTMLが引き起こす悪夢と防衛術
やあ。現場の最前線でコードを書き、時には炎上したインシデントの火消しに走るエンジニア諸君。今日はReact開発で誰もが一度は誘惑に駆られる、「HTMLの直接レンダリング」という禁忌について話そう。
「ReactはJSXで自動エスケープしてくれるから安全だよね?」という言葉を耳にするたびに、私は背筋が凍る思いがする。確かにReactはデフォルトで文字列をエスケープする。だが、その安心感こそが、攻撃者にとっての「一番の餌場」になっていることを知っているか?
1. なぜReactは「安全」だと言われているのか?
ReactのJSXは、埋め込まれた変数を文字列としてレンダリングする際に、自動的にエスケープ処理を行う。つまり、という文字列を {userInput} として渡せば、そのままブラウザ上に文字列として表示されるだけで、スクリプトは実行されない。これがReactの「デフォルトの防御力」だ。
しかし、現実はそう甘くない。CMSから取得したHTMLや、リッチテキストエディタの出力など、「どうしてもHTMLタグとしてレンダリングしたい」という要件に直面したとき、多くのエンジニアがこの悪魔の関数に手を出す。
dangerouslySetInnerHTML という劇薬
この関数名を見てくれ。Reactの公式ドキュメントですら「危険だ」と警告しているのに、なぜか開発者は「まあ、サニタイズしてるし大丈夫だろう」と高を括る。これがすべての始まりだ。
2. 攻撃者が狙う「盲点」:PoC(概念実証)
例えば、ユーザーのプロフィール編集画面で、管理者がHTML入力を許可している場合を想像してほしい。
// 危険な実装例
function UserProfile({ bioHtml }) {
// 開発者の思考:「サーバー側でサニタイズしてるから大丈夫」
return
}
この時、攻撃者は以下のようなペイロードを送り込む。
もしサーバーサイドのサニタイズが甘ければ、onerror イベントが発火し、あなたのWebアプリのセッションIDが攻撃者のサーバーへと送信される。これがクロスサイトスクリプティング(XSS)の真骨頂だ。「クライアント側の防御」を「サーバー側のチェック」の代わりにしてはいけない。
3. 完全防御のためのセキュアな実装
では、どうすればいいか?答えはシンプルだ。「HTMLを直接レンダリングしない」か、「徹底的に無害化する」かの二択だ。
推奨:DOMPurifyによる防御
どうしてもHTMLをレンダリングする必要がある場合は、dompurify ライブラリを使って、レンダリング直前に「安全なタグだけ」を通すフィルタリングを必ず実装しろ。
import DOMPurify from ‘dompurify’;
function SafeHtmlRenderer({ htmlContent }) {
// 1. DOMPurifyで悪意のある属性(onerrorなど)を徹底的に除去
// 2. USE_PROFILES: { html: true } でHTMLのみに限定
const cleanHtml = DOMPurify.sanitize(htmlContent, {
ALLOWED_TAGS: [‘b’, ‘i’, ‘em’, ‘strong’, ‘a’], // 許可するタグを最小限にする
ALLOWED_ATTR: [‘href’] // 許可する属性も最小限に
});
return (
);
}
4. 運用レイヤーでの多層防御(WAFの活用)
コードレベルの修正だけでは、ゼロデイ攻撃には対応できない。インフラエンジニアの視点も交えて、WAF(Web Application Firewall)での防御も必須だ。
例えば、AWS WAFを使用しているなら、以下のルールを適用することを強く推奨する。
- Core Rule Set (CRS) の有効化: XSSシグネチャを検知するルールを必ずONにする。
- カスタムルール:
dangerouslySetInnerHTMLを使うようなエンドポイントに対し、やonmouseover等の文字列が含まれるリクエストをBLOCKする設定を加えておく。
// AWS WAF ルール設定のイメージ(JSON)
{
"Name": "XSS-Block-Rule",
"Statement": {
"SqliMatchStatement": { "FieldToMatch": { "AllQueryArguments": {} }, "TextTransformations": [...] },
"XssMatchStatement": { "FieldToMatch": { "Body": {} }, "TextTransformations": [...] }
},
"Action": { "Block": {} }
}
最後に:セキュリティは「性悪説」で考えろ
現場で最も恐ろしいのは、「自分たちのコードは大丈夫」という根拠のない自信だ。
1. Reactの自動エスケープを過信しない。
2. dangerouslySetInnerHTML を使うときは、必ず DOMPurify をセットにする。
3. サーバーサイドでのサニタイズを怠らない(クライアントとサーバーの二重チェックが鉄則)。
インシデントは常に「想定外」の場所からやってくる。だが、基本を徹底し、常に最悪のケースを想定してコードを書くエンジニアがいれば、被害は最小限に抑えられるはずだ。
明日からの開発で、一度自分のコードを見直してほしい。「本当にこのHTMLレンダリングは必要か?」と。その問いかけこそが、あなたのプロダクトを守る最強の盾になる。
コメント