説明
React の dangerouslySetInnerHTML は、渡された HTML をそのまま DOM に描画します。ユーザー入力や信頼できない HTML をサニタイズせずに __html に渡すと、危険なイベントハンドラーや悪意のあるリンクなどが挿入され、XSS につながるおそれがあります。
想定される影響
- セッションやトークンの窃取、DOM の改ざんが起こる可能性があります。
- ユーザーのブラウザーで悪意のあるスクリプトが実行され、追加の API 呼び出しやフィッシング画面の挿入につながるおそれがあります。
- CMS、Markdown 変換、コメント描画など、HTML を出力する機能が攻撃に利用される可能性があります。
対処方法
dangerouslySetInnerHTMLの使用を最小限にし、可能なら通常の JSX 出力に置き換えてください。- HTML の描画が必要な場合は、DOMPurify などの実績がある HTML サニタイザーで処理した結果だけを使用してください。
- ユーザー入力を直接
__htmlに渡さないでください。
例
変更前
javascript
import React from "react";
export function Bad({ html }) {
return <section dangerouslySetInnerHTML={{ __html: html }} />;
}
変更後
javascript
import React from "react";
import DOMPurify from "dompurify";
export function Good({ html }) {
return <section dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />;
}
説明:
- 変更前: 入力をサニタイズせずに
dangerouslySetInnerHTMLに渡すため、XSS が発生するおそれがあります。 - 変更後: 描画する前に DOMPurify で HTML をサニタイズし、スクリプトや危険な属性を取り除きます。
striptags オプションの限界
javascript
import React from "react";
import stripTags from "striptags";
export function UnsafeAllowedTags({ html }) {
return <section dangerouslySetInnerHTML={{ __html: stripTags(html, "<img>") }} />;
}
export function UnsafeReplacement({ html }) {
return <section dangerouslySetInnerHTML={{ __html: stripTags(html, [], "<img src=x onerror=alert(1)>") }} />;
}
export function DefaultStripping({ html }) {
return <section dangerouslySetInnerHTML={{ __html: stripTags(html) }} />;
}
タグを許可すると危険な属性まで残る場合があり、置換文字列によって HTML を再び挿入することもできます。タグの除去を HTML のサニタイズと同じものとして扱わないでください。マークアップが不要なら、通常の JSX で {html} を出力する方法が簡単です。