Vue.jsの v-html は「時限爆弾」だ。なぜ多くの現場で事故が起きるのか
やあ。コードレビューの現場で v-html を見かけるたびに、私は「ああ、またパンドラの箱を開けようとしているのか」と頭を抱えたくなる。
Vue.jsはデフォルトでデータバインディングをエスケープしてくれる非常に優秀なフレームワークだ。しかし、開発者が「CMSから取得したHTMLをそのまま表示したい」「リッチテキストエディタの出力を反映させたい」という誘惑に負け、v-html を使う瞬間、セキュリティの防壁は崩れ去る。
今日は、この「便利な時限爆弾」とどう向き合い、どう安全に処理すべきか、現場の実践的な知見を共有しよう。
1. なぜ v-html が危険なのか:攻撃者の視点
攻撃者は、フロントエンドの v-html を見つけた瞬間、そこを「自分のコードを実行するための特等席」と見なす。
PoC:単純だが致命的な攻撃
例えば、ユーザーのプロフィール編集画面で入力された文字列が、そのまま v-html で描画されているとしよう。攻撃者は以下のような文字列を保存する。
たったこれだけで、このページを閲覧した他のユーザーのセッションCookieが攻撃者のサーバーへ送信される(XSS)。alert 程度なら可愛いものだが、実際には fetch APIを使って管理画面のデータを盗み出したり、勝手に投稿を行わせたりするスクリプトが注入される。
v-html を使うということは、「ブラウザに対して、私の預かり知らない悪意あるHTMLのパースを許可する」という契約を結ぶことに等しいんだ。
2. 鉄則:サニタイズなしのレンダリングは禁止
「エスケープすればいいんでしょ?」と安易に考えるな。HTMLタグを丸ごと除去するのではなく、「許可されたタグと属性のみをホワイトリスト方式で残す」のが鉄則だ。
これをフロントエンドだけで完結させようとするな。サーバー側でのサニタイズと、クライアント側での二段構えがプロの仕事だ。
実践:DOMPurifyを使った堅牢な実装
JavaScriptのフロントエンドでHTMLを扱うなら、DOMPurify 一択だ。これ以外のライブラリを自作したり、正規表現で頑張ろうとするのは時間の無駄であり、脆弱性の温床になる。
// npm install dompurify
import DOMPurify from ‘dompurify’;
// コンポーネント内での利用例
export default {
props: [‘rawHtml’],
computed: {
// 信頼できない入力はここで洗浄する
sanitizedHtml() {
return DOMPurify.sanitize(this.rawHtml, {
ALLOWED_TAGS: [‘b’, ‘i’, ‘em’, ‘strong’, ‘a’], // 必要なタグのみ許可
ALLOWED_ATTR: [‘href’] // 許可する属性も厳格に
});
}
},
template:
}
この実装の肝は、ALLOWED_TAGS と ALLOWED_ATTR を必要最小限に絞っていることだ。onerror 属性などはすべて自動的に削除されるため、攻撃の芽を摘むことができる。
3. インフラ側で「保険」をかける:CSPの導入
アプリケーションコードにミスがあった場合でも、被害を最小限に食い止める最後の砦が「Content Security Policy (CSP)」だ。
Nginxやサーバーのレスポンスヘッダーに以下の設定を追加しろ。これは現代のWeb開発において、もはや必須の「防弾チョッキ」だ。
Nginx設定例
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’; object-src ‘none’;”;
script-src 'self': 外部の悪意あるスクリプトや、インラインスクリプト()の実行をブロックする。object-src 'none': Flashなどのプラグイン実行を禁止する。
もし開発でどうしてもインラインスクリプトが必要なら、nonce を使う設計に移行すべきだが、まずは self で縛り上げるところから始めるのがいい。
4. チーフからの教訓:楽をするな、疑え
最後に一つだけ伝えておきたい。「データは常に汚れているもの」と前提してコードを書くことだ。
1. APIの戻り値は信用しない: バックエンドから来たデータでも、DBが汚染されている可能性はある。フロントエンドでのサニタイズを省略するな。
2. v-html を使う箇所を可視化する: チーム内で「v-html を使っているファイルは、コードレビューの際に必ずセキュリティチェックリストを適用する」というルールを徹底しろ。
3. UI/UXで解決できないか考える: そもそも v-html を使わずに、Markdownパーサー(markedなど)を使ってテキストを安全に変換する仕組みに置き換えられないか検討する。それが一番の根本解決だ。
セキュリティとは、技術的な実装以上に「疑う姿勢」の積み重ねだ。今日のコードから、その意識が反映されることを期待している。もし不明点があれば、いつでも私のデスクに来てくれ。共に堅牢なシステムを築こう。
コメント