react-markdown の生 HTML による XSS

react-markdown の生 HTML 描画による XSS

説明

React で react-markdown を使用するとき、信頼できない Markdown の生 HTML を描画すると、クロスサイトスクリプティング(XSS)につながるおそれがあります。現在の react-markdown は、既定で HTML をエスケープするか無視します。rehype-raw で HTML の解析を有効にし、rehype-sanitize などでサニタイズしない場合、悪意のあるタグや属性が出力に入り込む可能性があります。旧バージョンの allowDangerousHtml や escapeHtml={false} も同じ危険を生む場合があります。

想定される影響

  • 攻撃者が、ページからアクセスできる Cookie、セッション情報、その他の機密情報を取得するおそれがあります。
  • ユーザーのブラウザーで悪意のあるコードが実行され、さらなる攻撃につながる可能性があります。
  • 悪意のあるコンテンツによって、サイトへの信頼が損なわれる場合があります。

対処方法

  • 信頼できない Markdown には rehype-raw を使用せず、既定の描画を維持してください。
  • 生 HTML が必要な場合は、rehype-raw の後に rehype-sanitize を適用し、許可するタグと属性のスキーマを最小限にしてください。
  • ユーザーが入力した Markdown や描画するデータを検証し、サニタイズしてください。
  • URL 変換オプションを変更する場合は、javascript:、data: などの危険なスキームを拒否してください。

例

変更前

tsx
import ReactMarkdown from "react-markdown";
import rehypeRaw from "rehype-raw";

function UnsafeComponent({ markdown }) {
  // 危険: 信頼できない Markdown の生 HTML をそのまま解析
  return <ReactMarkdown rehypePlugins={[rehypeRaw]}>{markdown}</ReactMarkdown>;
}

変更後

tsx
import ReactMarkdown from "react-markdown";
import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";

function SafeComponent({ markdown }) {
  // より安全な既定の動作: 生 HTML を解析しない
  return <ReactMarkdown>{markdown}</ReactMarkdown>;
}

function SafeHtmlComponent({ trustedButNeedsHtml }) {
  // HTML を許可する場合もサニタイズを適用
  return (
    <ReactMarkdown rehypePlugins={[rehypeRaw, rehypeSanitize]}>
      {trustedButNeedsHtml}
    </ReactMarkdown>
  );
}

説明:

  • 変更前: rehype-raw だけを使用すると、Markdown 内の生 HTML が React 要素に変換されます。サニタイズされていない HTML が、コードを実行できる形で DOM に入る可能性があります。
  • 変更後: 既定の描画では生 HTML を実行しません。HTML の描画が必要な場合も、rehype-sanitize で許可したタグと属性だけを残し、XSS の危険を減らします。

参考資料