Reactの「自動エスケープ」を過信してはいけない:dangerouslySetInnerHTMLが引き起こす悪夢と防衛術
やあ、現場の最前線でコードと向き合うエンジニア諸君。
「Reactを使っているからXSS(クロスサイトスクリプティング)は自動的に防げる」――そう安心しきっていないか?
確かに、ReactはデフォルトでJSX内の変数を文字列として処理し、HTMLエンティティに変換してくれる。だが、それはあくまで「Reactの流儀」に従っている場合に限る。
今日は、開発者が利便性のために使う、しかし最も脆弱性を生み出しやすい禁断のAPI、dangerouslySetInnerHTMLの「本当の怖さ」と、それをどう封じ込めるかという実務の話をしよう。
—
1. なぜReactは安全なのか?そして、なぜ壊されるのか
Reactが標準で安全なのは、{name} のように変数を埋め込む際、Reactが内部的に document.createTextNode を呼び出しているからだ。タグやスクリプトは単なる「文字列」として描画されるため、実行されることはない。
しかし、CMSから取得したHTMLコンテンツを表示したい、リッチテキストエディタの出力結果を出したい、といった場面で、開発者はしばしば dangerouslySetInnerHTML というプロパティに手を出す。
このプロパティは、Reactの守護壁を物理的に破壊する。「私はHTMLを直接DOMに流し込むから、セキュリティなんて知ったことか」とReactに命令しているのと同じだ。この瞬間、Reactの自動エスケープ機能は完全に無力化される。
—
2. 実践:攻撃者はこうしてシステムを乗っ取る(PoC)
もし、ユーザーが投稿したコメントをサニタイズせずに dangerouslySetInnerHTML で表示していたらどうなるか。攻撃者は以下のようなペイロードを仕込む。
このコードがレンダリングされた瞬間、 タグの読み込みエラーが発生し、onerror イベントが発火する。結果として、管理者のセッションCookieが攻撃者のサーバーに送信されることになる。これがXSSの基本であり、多くのプロダクトがこれで沈んできた。
—
3. 防御の鉄則:DOMPurifyによる武装
dangerouslySetInnerHTML を使うなら、「汚れたHTMLを無害化する」というプロセスを自前で実装するしかない。車輪の再発明は避け、業界標準である dompurify を使え。これは議論の余地がないルールだ。
安全な実装サンプル(React)
まず、ライブラリをインストールする。
npm install dompurify
npm install @types/dompurify --save-dev
そして、以下のように実装する。
import React from ‘react’;
import DOMPurify from ‘dompurify’;
const SafeHTMLRenderer = ({ rawHtml }) => {
// DOMPurify.sanitize で悪意のあるスクリプトを除去する
// ALLOWED_TAGS や ALLOWED_ATTR で必要最小限の許可に絞るのがコツだ
const cleanHtml = DOMPurify.sanitize(rawHtml, {
ALLOWED_TAGS: [‘b’, ‘i’, ‘em’, ‘strong’, ‘p’],
ALLOWED_ATTR: [] // 属性を空にすればonerror等のイベントも排除できる
});
return (
);
};
ポイント: 許可するタグは最小限にすること。「何でも許可する」設定でサニタイズした気になっているエンジニアが多いが、それは「鍵をかけていない金庫」と同じだ。
—
4. 追撃の一手:CSP(Content Security Policy)による多層防御
アプリケーション側の対策だけでは足りない。万が一、脆弱性が残っていた場合の「最後の防衛線」がCSPだ。Nginxやサーバー側で以下のHTTPヘッダーを付与しておけ。
Nginxの設定例:
信頼できないインラインスクリプトの実行を禁止する
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’; object-src ‘none’;”;
これにより、仮に攻撃者が注入した タグや onerror イベントがあっても、ブラウザが「ポリシー違反」として実行をブロックしてくれる。
---
セキュリティチーフからの助言
いいか、エンジニアリングにおいて「便利さ」と「安全性」は常にトレードオフだ。dangerouslySetInnerHTML は便利なツールだが、それは諸刃の剣だ。
- レビューの厳格化: コードレビューで
dangerouslySetInnerHTMLを見かけたら、まず「なぜこれが必要なのか」「サニタイズは適切か」を問い詰めろ。 - デフォルトの否定: 「とりあえず動く」コードを正解とするな。その実装が、将来の自分や顧客を窮地に追い込むリスクを常に想像しろ。
技術は常に進化するが、攻撃者が狙うのはいつだって「面倒くさがって省略したチェック」の隙間だ。堅牢なシステムを作るのは魔法ではなく、こうした地道な防衛の積み重ねに他ならない。
現場からは以上だ。コードを書き直す準備はできたか?
コメント