dangerouslySetInnerHTMLとは?
dangerouslySetInnerHTML
はReactのプロパティで、HTML形式の文字列をHTMLとして扱い、そのHTMLをコンポーネント内に直接挿入することができます。
外部から取得したHTMLコンテンツを表示する場合などに使用します。
<div dangerouslySetInnerHTML={{ __html: '<p>some html</p>' }} />
上記のコードは<p>some html</p>
というHTMLを<div>
タグの中に挿入し、結果としてブラウザに"some html"というテキストを表示します。
しかし、dangerouslySetInnerHTML
は名前にdangerouslyとある通り、使用には危険が伴うため注意が必要です。
公式ドキュメントはこちら↓
dangerouslySetInnerHTMLの危険性
1. XSS攻撃のリスク
dangerouslySetInnerHTML
の最大の危険性は、XSS(クロスサイトスクリプティング)攻撃のリスクがあることです。
攻撃者はHTML文字列に悪意のあるスクリプトを埋め込み、ユーザーがフォームに入力した個人情報を盗んだり、ページを改ざんしたりといったことができてしまう可能性があります。
const userInput = '<img src="x" onerror="alert(\'XSS Attack\')" />';
<div dangerouslySetInnerHTML={{ __html: userInput }} />
上記のコードでは、ユーザーからの入力をそのままdangerouslySetInnerHTML
に渡しています。もしも悪意のあるスクリプトが埋め込まれていたらそれが実行されてしまうかもしれません。
dangerouslySetInnerHTML
は内部にinnerHTML
と同じ機能を持っているので、scriptタグはデフォルトで無効化されます。しかしイベントハンドラなどは実行できてしまうのでおまけ程度に考えておきましょう。
対策:サニタイズ処理を行う
XSS攻撃を回避するためには、ユーザーからの入力をそのまま表示するのではなくサニタイズをする必要があります。
サニタイズとは「消毒」という意味で、悪意のあるスクリプトを無害なものに変換することです。
また、サニタイズはセキュリティに直結する重要な処理なので、自前で実装するのではなくきちんと整備されたライブラリを使用することが推奨されています。
例えば、DOMPurify
というライブラリを使用してサニタイズを行う場合は下記のようになります。
https://github.com/cure53/DOMPurify
import DOMPurify from 'dompurify';
const sanitizedHTML = DOMPurify.sanitize(userInput);
<div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />
DOMPurify.sanitize
関数は入力されたHTML文字列を解析し、悪意のあるスクリプトを取り除きサニタイズします。その結果、安全に表示できるHTML文字列が生成されるのでXSS攻撃を防ぐことができます。
2. 直接DOMを操作することになる
Reactは仮想DOMを使って効率的にレンダリングを行いますが、dangerouslySetInnerHTML
を使うとこの仮想DOMを通らずに直接実際のDOMを変更してしまいます。
これにより仮想DOMと実際のDOMに差分が生まれ、パフォーマンスの低下や、予期しない動作やバグを引き起こしてしまう可能性があります。
しかし、これはdangerouslySetInnerHTML
で挿入されたHTMLがアプリケーション内で何らかの対話性を持つ場合に発生します。
静的なHTMLコンテンツを表示することだけが目的であればこのリスクは軽減されます。
仮想DOMとは実際のDOMの軽量なコピーで、Reactが効率的なレンダリングを行うために使用します。変更がある度に新旧のDOMを比較し、変更箇所だけを実際のDOMに反映します。
対策:ライブラリを使用して仮想DOMとして利用する
この問題を解決するためには、HTMLを仮想DOMとして扱うことができるライブラリを使用することが推奨されます。例えば、html-react-parser
というライブラリを使用すると、HTMLをReactの要素に変換することができます。
https://github.com/remarkablemark/html-react-parser
import parse from 'html-react-parser';
const reactElement = parse(userInput);
<div>{reactElement}</div>
このコードでは、parse関数を使用してユーザーからの入力をReactの要素に変換してから表示しています。これにより、Reactの仮想DOMを利用することができ、パフォーマンスの低下やバグの発生を防ぐことができます。
仮想DOMとして扱うライブラリを導入することによってdangerouslySetInnerHTML
を使用しないことになりますが、XSS攻撃の危険性は残っています。なのでサニタイズ処理は別途必要なことに注意しましょう。
まとめ
取得したHTML文字列が信頼できるもので、dangerouslySetInnerHTML
によって挿入されるHTMLが静的なコンテンツに限定されている場合、dangerouslySetInnerHTML
を使用することは概ね安全だと言えるでしょう。
しかし、そうでない場合はサニタイズ処理や仮想DOM化といった適切な対策が必要となってきます。
ただ、外部ライブラリを使用する場合はそのライブラリの更新やセキュリティフィックスに依存することとなり、メンテナンス負荷を増やす可能性があるのでその点は注意が必要です。