Markdownパーサーの深淵:DOMPurifyが「銀の弾丸」ではない理由と、多層防御のアーキテクチャ
多くの開発者がMarkdownをHTMLに変換する際、markedやmarkdown-itを導入し、その出力をそのままDOMに注入するような「アーキテクチャ上の爆弾」を抱えている。そして、脆弱性診断のレポートで「DOMPurifyを通せ」と指摘され、思考停止して実装を完了させる。これが現代のWebアプリケーションにおけるXSSの温床だ。
今日は、その「表面的な対策」の先にある、攻撃者が狙う盲点と、真に堅牢な変換パイプラインの設計思想について語ろう。
—
1. パーサーの不整合が生む「想定外の解釈」
Markdownパーサーの脆弱性の根源は、仕様(CommonMark)と実装(各ライブラリの拡張機能)の乖離にある。特にunsafe-link(javascript:スキーム)や、属性ベースのインジェクションは、パーサーの正規化プロセスとブラウザのHTMLパースエンジンの解釈の差異を突く。
攻撃者は、パーサーが「安全なテキスト」とみなした断片を、ブラウザが「実行可能なコンテキスト」として再解釈する瞬間を狙っている。例えば、特定のエンコーディングを混ぜたペイロードが、パーサーをすり抜け、DOMPurifyのサニタイズ処理を経てから、ブラウザのパーサーによってデコードされ、JSが起動する。これは、サニタイズの「タイミング」を誤っているから起きる必然的な事象だ。
2. DOMPurifyを「信頼しすぎない」ための設計
DOMPurifyは強力だが、設定なしで使えば「最小限の権限」という原則に反する。デフォルト設定は汎用性を優先しており、あなたのアプリケーションが許可すべきでないタグまで通してしまう可能性がある。
以下のコードは、単にDOMPurifyを通すのではなく、「ビジネスロジックに不要な要素を徹底的に排除する」という思想に基づいたパイプラインの雛形だ。
import DOMPurify from ‘dompurify’;
import { marked } from ‘marked’;
/
- セキュアなMarkdown変換パイプライン
- 1. 外部入力をトークナイズし、ASTを生成
- 2. 許可されたタグ/属性のみをホワイトリスト方式で抽出
- 3. 必要に応じてコンテキストに応じたエンコーディングを強制
/
const secureMarkdownParser = (markdownInput) => {
// 1. MarkdownをHTMLへ変換
const rawHtml = marked.parse(markdownInput);
// 2. DOMPurifyの厳格な構成
// なぜこれが必要か:デフォルトでは許可されることが多い ‘target’ や ‘rel’ も制限し、
// プロトコルも ‘http’, ‘https’, ‘mailto’ 以外を徹底的に排除する。
return DOMPurify.sanitize(rawHtml, {
ALLOWED_TAGS: [‘p’, ‘br’, ‘strong’, ‘em’, ‘ul’, ‘li’, ‘code’, ‘pre’], // 必要最小限に絞る
ALLOWED_ATTR: [‘href’, ‘title’], // 必要な属性のみ
ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto|ftp):|[^:/?#](?:[/?#]|$))/i, // javascript: を遮断
RETURN_TRUSTED_TYPE: true // Trusted Types APIの利用を前提とする
});
};
3. 生成AI時代のプロンプト・インジェクションとの交差点
現在、多くのMarkdownベースのCMSには、AIによる自動生成機能が組み込まれている。ここで発生するのが「間接的プロンプトインジェクション」だ。
AIが生成した「Markdown」自体に、攻撃者が意図した細工(隠しリンク、Base64エンコードされたJSペイロード)が含まれていた場合、それをそのままパーサーに流し込むのは自殺行為に近い。
我々がとるべき防衛戦略は「検証と無害化の分離」だ:
- 入力のサニタイズ: AIからの出力を受け取った直後に、Markdownパーサーに渡す前にLLM自身のガードレイル機能(入力・出力フィルタリング)で構造チェックを行う。
- レンダリング時のサンドボックス化:
のsandbox属性(allow-scriptsを付与しない)を活用し、HTMLのレンダリング領域をメインのDOMから隔離する。これが現在の「防御の要」だ。
4. 最後に:インシデントハンドラーの視点
私が現場で目にする深刻な脆弱性の多くは、技術的な不足よりも「コンテキストの欠如」に起因している。
「HTMLのサニタイズをすれば安全だ」と信じ込むのは、鍵をかけたドアの横の窓が全開であることに気づいていないのと同じだ。Markdownの変換処理は、アプリケーション全体のデータフローの中で最も「不純物」が混入しやすい地点である。
アーキテクトへ告ぐ:
DOMPurifyの設定値一つ一つに対して、「なぜこのタグを許可するのか?」「この属性は本当に必要か?」と自問自答せよ。その「執念深い疑い」こそが、CVEの海を渡り切るための唯一の羅針盤となる。
セキュリティは、ツールを導入して終わるものではない。データがシステムを通過する各フェーズにおいて、どのように「信頼(Trust)」を担保し、あるいは「不審(Distrust)」を排除するかという、アーキテクチャそのものの設計思想に宿るのだ。
コメント