ReactのdangerouslySetInnerHTMLをDOMPurifyで安全に使う

公開日:
目次

Reactで外部から取得したHTMLを表示したいことがあります。マークダウンをHTMLに変換した結果や、CMSから取得したリッチテキストなどです。このときdangerouslySetInnerHTMLを使いますが、名前の通り危険が伴います。DOMPurifyを使ってこの問題を解決したので備忘録です。

XSS脆弱性とは

XSS(クロスサイトスクリプティング)は、Webアプリケーションの脆弱性の一つです。攻撃者が悪意のあるスクリプトを注入し、他のユーザーのブラウザで実行させます。

たとえば、ブログ記事のコメント欄で以下のような入力があった場合を考えます。

<script>alert('XSS攻撃!')</script>

これをそのまま表示すると、スクリプトが実行されてしまいます。攻撃者はこれを悪用して、ログイン中のユーザーのCookieを盗んだり、偽のログインフォームを表示してパスワードを取得したりできます。

dangerouslySetInnerHTMLとは

Reactは通常、文字列をそのままテキストとして表示します。<b>太字</b>という文字列を渡しても、タグがエスケープされてそのまま表示されます。

HTMLとして解釈させたい場合はdangerouslySetInnerHTMLを使います。

// そのまま表示 → &lt;b&gt;太字&lt;/b&gt;
<div>{htmlString}</div>

// HTMLとして解釈 → **太字**
<div dangerouslySetInnerHTML={{ __html: htmlString }} />

名前に「dangerously」とあるように、信頼できないHTMLを渡すとXSS脆弱性になります。マークダウンから変換したHTMLやCMSからのコンテンツなど、外部データを表示する場合はサニタイズが必要です。

HTMLサニタイズライブラリの比較

HTMLをサニタイズするライブラリはいくつかあります。

ライブラリ サイズ 特徴
DOMPurify 約10KB 最も広く使われている。XSS攻撃パターンを網羅
sanitize-html 約50KB サーバーサイドでも動作。設定が柔軟
xss 約30KB 中国語ドキュメントが充実

DOMPurifyを選ぶ理由は以下の通りです。

  • 軽量で高速
  • セキュリティ専門家によるメンテナンス
  • 新しいXSSベクターへの迅速な対応
  • TypeScriptの型定義が公式提供

DOMPurifyとは

DOMPurifyは、HTMLをサニタイズ(無害化)するJavaScriptライブラリです。危険なタグや属性を除去し、安全なHTMLだけを残します。

インストール

npmでインストールします。

npm install dompurify

TypeScriptを使う場合は型定義もインストールします。

npm install -D @types/dompurify

基本的な使い方

sanitize メソッドにHTMLを渡すだけです。

import DOMPurify from 'dompurify'

const dirtyHtml = '<script>alert("XSS")</script><p>安全なテキスト</p>'
const cleanHtml = DOMPurify.sanitize(dirtyHtml)

console.log(cleanHtml)
// 出力: <p>安全なテキスト</p>

<script> タグが除去され、安全な <p> タグだけが残ります。

Reactでの実装例

dangerouslySetInnerHTMLとDOMPurifyを組み合わせたコンポーネントを作ります。

import DOMPurify from 'dompurify'

type Props = {
  html: string
}

export const SafeHtml = ({ html }: Props) => {
  return (
    <div
      dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }}
    />
  )
}

使い方はシンプルです。

// マークダウンから変換したHTMLを表示
<SafeHtml html={markdownToHtml(content)} />

// CMSから取得したリッチテキストを表示
<SafeHtml html={article.body} />

設定のカスタマイズ

デフォルト設定で多くのケースに対応できますが、必要に応じてカスタマイズも可能です。

許可するタグを制限する

const cleanHtml = DOMPurify.sanitize(dirtyHtml, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
  ALLOWED_ATTR: ['href']
})

すべての属性を除去する

const cleanHtml = DOMPurify.sanitize(dirtyHtml, {
  ALLOWED_ATTR: []
})

テキストのみを抽出する

const textOnly = DOMPurify.sanitize(dirtyHtml, {
  ALLOWED_TAGS: [],
  ALLOWED_ATTR: []
})

留意点

DOMPurifyを使っても完全な安全性は保証されません。以下の点に注意してください。

  • サーバーサイドでも検証する :クライアントサイドだけでなく、サーバーサイドでも入力を検証してください。
  • 最新版を使う :新しいXSS攻撃パターンに対応するため、定期的にアップデートしてください。
  • CSPと組み合わせる :Content Security Policy(CSP)を設定し、多層防御を実現してください。

参考資料

DOMPurifyの詳細な設定オプションは公式READMEを参照してください。

XSS脆弱性についてはOWASPのガイドが参考になります。