Vue.jsの v-html という「劇薬」:その利便性の裏に潜むXSSの罠
現場でコードレビューをしていると、必ずと言っていいほど遭遇するのが v-html の安易な使用だ。CMSで作成されたリッチテキストを表示したい、あるいは外部APIから受け取ったHTMLをそのまま描画したい。理由は様々だが、セキュリティの観点から言えば、それは「ブラウザに対して、信頼できない入力をそのままDOMとして実行させる」という、極めて危険な行為に他ならない。
今日は、Vue.jsにおけるXSSの核心と、その「劇薬」とどう付き合うべきか、泥臭い実務の話をしよう。
—
1. なぜ v-html は危険なのか?
Vue.jsの強力な武器である「テンプレート構文」は、デフォルトでは {{ }}(マスタッシュ構文)を使う限り、変数の内容をHTMLエスケープしてくれる。しかし、v-html を使った瞬間、Vueはその防御バリアを解除する。
攻撃者が狙う「DOMの隙間」
例えば、ユーザーのプロフィール欄を以下のように実装しているとしよう。
ここで攻撃者が userProvidedContent に以下のようなペイロードを送り込んだらどうなるか。
ブラウザは src="x" という読み込みに失敗し、即座に onerror イベントを発火させる。結果、管理者のセッションクッキーが盗まれる、あるいは悪意のあるスクリプトがWebアプリのコンテキストで実行される。これが「DOM型XSS」の典型的な入り口だ。
—
2. 防御の鉄則:サニタイズなしにレンダリングするな
「信頼できるソースからしかデータは来ない」という性善説は、セキュリティの世界では通用しない。社内ツールであっても、バックエンドのバリデーションが甘い箇所があれば、そこが侵入経路になる。
推奨:DOMPurifyによるホワイトリスト制御
v-html を使うなら、必ず「サニタイズ(無害化)」を挟むこと。自前で正規表現を書いてHTMLを除去しようとするのは、「セキュリティの再発明」であり、100%脆弱性が残ると心得ておけ。
圧倒的な信頼性を誇るライブラリ DOMPurify を使うのが正解だ。
実装サンプル (Vue 3 / JavaScript)
import DOMPurify from ‘dompurify’;
// 算出プロパティでサニタイズ済みのHTMLを生成する
computed: {
sanitizedContent() {
// 許可するタグや属性を詳細に設定可能
return DOMPurify.sanitize(this.userProvidedContent, {
ALLOWED_TAGS: [‘b’, ‘i’, ‘em’, ‘strong’, ‘a’],
ALLOWED_ATTR: [‘href’]
});
}
}
これだけで、onerror や タグは容赦なく削除される。
---
3. インフラレイヤーでの防衛線:CSP(Content Security Policy)
アプリケーション側で完璧に制御しているつもりでも、ゼロデイや見落としは起こり得る。最後に守るのは、Webブラウザそのものの機能だ。CSP(Content Security Policy)をHTTPヘッダーとして設定し、「どこから来たスクリプトなら実行していいか」を定義せよ。
Nginxでの設定例
以下の設定をNginxのコンフィグに追加することで、インラインスクリプト(v-html経由の不正なものを含む)の実行を制限できる。
CSPの基本設定
'self':自サイト内のスクリプトのみ許可
'unsafe-inline' は極力避ける(XSSリスクが跳ね上がるため)
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none';";
もしアプリケーションの仕様上、どうしてもインラインスクリプトが必要なら、nonce(使い捨てトークン)を活用した制御を行うべきだ。これができれば、攻撃者が外部から注入したスクリプトはブラウザによって即座にブロックされる。
---
最後に:セキュリティは「諦めない」こと
「これくらいなら大丈夫だろう」という慢心が、大規模な情報漏洩の引き金になる。
1. デフォルトで v-html を避ける(本当に必要か再考せよ)。
2. 使うなら必ず DOMPurify を通す(ブラックリスト方式は厳禁)。
3. CSPでブラウザ側の防護壁を固める。
コードを書くとき、常に「攻撃者がこの入力を操作したらどうなるか?」を想像してほしい。技術的な完璧さは難しいが、「防御の多層化」を意識するだけで、君のプロダクトは劇的に堅牢になる。
次のレビューで、もし v-html を見つけたら、この記事を思い出して優しく問い詰めてやってくれ。「これ、サニタイズしてるか?」とな。
コメント