DOMPurifyで防ぐ「盲点」:XSSは単なる「アラートが出る」問題ではない
現場のエンジニア諸君、今日もコードの最前線でお疲れ様。
セキュリティの現場に長くいると、「クロスサイトスクリプティング(XSS)? ああ、を投げればいいんでしょ?」と、高を括っている若手に時折出会う。だが、現実はそんなに甘くない。
攻撃者は、ブラウザのパーサーが「これはコードだ」と誤認する、もっと泥臭くて巧妙な「タグの隙間」を狙ってくる。特に現代のSPA(Single Page Application)やCMSのように、ユーザーが入力したHTMLを動的にレンダリングするアプリケーションは、DOMを直接操作する過程で常に「自爆」の危険と隣り合わせだ。
今日は、そんな泥沼から我々を救い出してくれる「DOMPurify」の正しい使い方について、教科書には載っていない実践的な知見を共有しよう。
—
なぜDOMPurifyなのか? 〜「ブラックリスト」は死に体である〜
まず大前提だ。正規表現や自作の置換関数でHTMLをサニタイズしようとするのは「ザルで水をすくう」のと同じだと心に刻んでほしい。
例えば、攻撃者は次のようなペイロードを平気で投げてくる。
これらを個別に弾こうとすれば、ブラックリストはすぐに肥大化し、メンテナンス不能になる。DOMPurifyが優れているのは、ブラウザのDOM解析エンジンを利用して「安全な要素と属性のみをホワイトリスト方式で通過させる」という、極めて堅牢な設計思想にあるからだ。
—
【実戦】DOMPurifyのセキュアな実装パターン
DOMPurifyを導入する際は、デフォルト設定を過信してはいけない。アプリケーションの要件に合わせて、「徹底的に絞り込む」のがプロの流儀だ。
以下は、ReactやVue、あるいはプレーンなJavaScript環境で活用できる、最も推奨される実装例だ。
JavaScript実装サンプル
import DOMPurify from ‘dompurify’;
/
- ユーザー入力を受け取り、安全なHTMLとして返す関数
- @param {string} dirtyHtml – 信頼できない入力文字列
- @returns {string} – サニタイズ済みの安全なHTML
/
function sanitizeInput(dirtyHtml) {
return DOMPurify.sanitize(dirtyHtml, {
// 1. 基本設定:許可リストを最小限に絞る
ALLOWED_TAGS: [‘b’, ‘i’, ‘em’, ‘strong’, ‘a’, ‘p’, ‘br’],
ALLOWED_ATTR: [‘href’, ‘title’, ‘target’],
// 2. リンクの安全性を強制する
// javascript: プロトコルなどを完全にブロックする
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
// 3. リンクは必ず新しいタブで開き、リファラを遮断(セキュアなリンク設計)
ADD_ATTR: [‘target’],
FORBID_TAGS: [‘style’, ‘script’, ‘iframe’, ‘object’],
// 4. 解析結果が不審な場合にエラーを投げる(厳格モード)
RETURN_DOM: false,
RETURN_DOM_FRAGMENT: false,
});
}
// 利用イメージ
const userInput = ‘Hello‘;
const cleanHtml = sanitizeInput(userInput);
// 結果: Hello (imgタグは属性を含めて綺麗に消える)
—
運用で絶対に忘れてはいけない「3つの鉄則」
コードを書くだけがセキュリティではない。運用フェーズでこれを維持するための防衛線を張っておく必要がある。
1. CSP(Content Security Policy)との二段構え
DOMPurifyを突き抜けるような未知のゼロデイ脆弱性が見つかったとしても、CSPが設定されていれば被害を最小限に抑えられる。Nginx等で以下のヘッダーを必ず付与せよ。
Nginx設定例
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’; object-src ‘none’;”;
特に script-src 'unsafe-inline' を許可しない運用を目指すのが、現代のWebエンジニアの責務だ。
2. ライブラリのアップデートを自動化する
DOMPurifyのようなサニタイズライブラリは、ブラウザの仕様変更(バイパス手法)を追うために頻繁にアップデートされる。npm audit や Dependabot を導入し、常に最新版を追いかける体制を構築すること。
3. DOMPurifyだけで完結させない(多層防御)
もしバックエンドにPHPやPythonを使っているなら、サーバーサイドでもバリデーションを行うのが鉄則だ。クライアントサイドのサニタイズは「UX向上のための即時フィードバック」と割り切り、最終的な保存データはサーバー側で HTMLPurifier (PHP) 等を用いて再精査する。「クライアントのデータは全て悪意がある」という性悪説こそが、我々エンジニアの生存戦略だ。
—
最後に:セキュリティは「終わりのない旅」だ
セキュリティ対策は、一度コードを書いて終わりではない。攻撃者は常に「次の隙間」を探している。
今回紹介したDOMPurifyの実装は、あくまで「最低限の防御壁」に過ぎない。しかし、この壁があるかないかで、インシデント発生時の被害規模は劇的に変わる。
君たちが書くコードが、誰かの大切な情報を守る盾になる。その誇りを持って、日々の開発に取り組んでほしい。もし「この実装で本当に大丈夫か?」と不安になったら、いつでもこのブログを読み返してくれ。
現場からは以上だ。健闘を祈る。
コメント