React開発の「禁じ手」をどう安全に扱うか:dangerouslySetInnerHTMLと向き合う全技術
現場でコードレビューをしていると、必ず一度は遭遇するのがdangerouslySetInnerHTMLというプロパティだ。React開発者にとって、この名前は「危険なのは分かっているが、どうしてもHTMLをそのまま流し込みたい」という誘惑の象徴だろう。
だが、はっきり言おう。このプロパティを「なんとなく」使っているエンジニアは、爆弾を抱えて走り回っているのと同じだ。
今日は、なぜこれがXSS(クロスサイトスクリプティング)の温床になるのか、そしてどうすれば安全に運用できるのか。綺麗事抜きの「現場の作法」を解説する。
—
1. なぜdangerouslySetInnerHTMLは危険なのか
Reactの最大の特徴は、デフォルトで変数をエスケープしてレンダリングする「自動防御機能」にある。
と書けば、たとえuserInputにタグが含まれていても、Reactはそれを単なる文字列として扱い、ブラウザはそれを実行しない。
しかし、dangerouslySetInnerHTMLを使うと、その防御壁を自ら取り払うことになる。
攻撃者が見ている「盲点」
攻撃者は、あなたが「ここは管理画面だから安全だ」「CMSから取得したデータだから信頼できる」という甘い期待を抱いていることを知っている。
例えば、プロフィール編集画面の「自己紹介欄」に以下のような値を保存されたらどうなるか。
これをdangerouslySetInnerHTMLでそのまま描画すると、ブラウザは画像読み込みエラーを検知し、即座にonerror属性内のJavaScriptを実行する。これがセッションIDの奪取や、バックグラウンドでの不正なAPIコールに繋がる。これが「格納型XSS」の典型的な入り口だ。
---
2. 鉄則:DOMPurifyなしのレンダリングは「悪」
「サーバーサイドでバリデーションしているから大丈夫」という言い訳は通用しない。フロントエンドのレンダリング時には、常に「レンダリング直前のサニタイズ」を行うのがセキュリティの黄金律だ。
ここで唯一信頼できるツールが「[DOMPurify](https://github.com/cure53/dompurify)」だ。これは、世界中のセキュリティリサーチャーが信頼を寄せるライブラリであり、HTMLの構造を解析し、許可されたタグと属性以外の「危険なパーツ」を徹底的に排除してくれる。
セキュアな実装パターン
以下は、ReactでdangerouslySetInnerHTMLを安全に使うための実務的なコンポーネント実装だ。
import React from 'react';
import DOMPurify from 'dompurify';
/
- 安全にHTMLをレンダリングするためのラッパーコンポーネント
- @param {string} html - 外部から取得した汚染の可能性があるHTML文字列
/
const SafeHtmlRenderer = ({ html }) => {
// 1. DOMPurifyでサニタイズを実行
// 2. 許可されたタグ以外は完全に除去される
const sanitizedHtml = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'], // 許可するタグを最小限に絞る
ALLOWED_ATTR: ['href', 'target'], // 許可する属性も最小限に
});
return (
);
};
export default SafeHtmlRenderer;
ポイント: ALLOWED_TAGSとALLOWED_ATTRをホワイトリスト方式で厳格に定義すること。ここをケチると、脆弱性は再発する。
---
3. インフラ・サーバー側での「二重の防波堤」
アプリ側の対策は必須だが、もしもの時のために「多層防御」を敷くのがプロの仕事だ。
WAFの活用 (AWS WAFの例)
AWS WAFを使用しているなら、AWSManagedRulesCommonRuleSetを必ず有効にしてほしい。これには「Cross-site Scripting (XSS)」を検知するためのシグネチャが含まれており、リクエスト段階で怪しいペイロードを弾いてくれる。
CSP(Content Security Policy)による封じ込め
XSSが万が一発生してしまった時の「最後の砦」がCSPだ。HTTPレスポンスヘッダーに以下を設定することで、たとえスクリプトが注入されても実行させないようにできる。
Nginxの設定例:
インラインスクリプトの実行を禁止する強力なCSP設定
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none';";
script-src 'self':信頼できるソースからのスクリプトしか実行させない。object-src 'none':プラグイン経由の攻撃を無効化する。
---
最後に:エンジニアが持つべき「疑心暗鬼」
セキュリティの世界では、「100%安全」などという言葉は詐欺師の口からしか出てこない。
ReactのdangerouslySetInnerHTMLは、名前の通り「危険」を明示している。それを使う時は、「自分は今、アプリの最も脆い部分に穴を開けている」という自覚を持つことだ。
1. 本当にHTMLレンダリングが必要か?(Markdownや単純なテキストで代替できないか?)
2. DOMPurifyの最新版を使っているか?
3. ホワイトリストは適切に制限されているか?
この3点を自問自答するだけで、あなたの書くコードは格段に堅牢になる。技術は魔法ではない。泥臭い確認と、疑い深い設計の積み重ねこそが、最高峰の防御壁を作るのだ。
コメント