「v-html」は劇薬だ:Vue.jsにおけるXSSの盲点と、泥臭いまでの防衛術
現場でコードレビューをしていると、必ずと言っていいほど目にするのが v-html の安易な利用だ。「CMSから取得したリッチテキストをそのまま表示したい」「デザイン要件でHTMLタグが含まれる文字列を流し込みたい」。動機は理解できる。だが、それがどれほどの地雷を埋め込んでいるか、自覚しているエンジニアは驚くほど少ない。
今日は、Vue.jsにおける v-html がなぜ「即時インシデントへの招待状」なのか、そして我々プロが現場でどうやってこのリスクを封じ込めているのか、その現実的な解法を共有する。
—
1. なぜ v-html は「終わりの始まり」なのか
Vue.jsは、デフォルトではデータを「テキスト」としてレンダリングする。{{ mustache }} 構文を使えば、中身が タグであっても、Vueはそれをただの文字列としてエスケープ処理し、ブラウザ上で実行させない。
しかし、v-html を使った瞬間、その防護壁は解除される。Vueは「この開発者はHTMLを意図的にレンダリングしたいのだな」と解釈し、生のDOMとして解釈させる。これがクロスサイトスクリプティング(XSS)の入り口だ。
攻撃者視点:PoC(概念実証)の恐怖
もし、ユーザーが入力したコメントやプロフィール文がそのまま v-html に流し込まれているとしたら、攻撃者は以下のようなペイロードを送り込む。
このコードが保存され、他のユーザー(あるいは管理者)がそのページを表示した瞬間に何が起きるか。onerror イベントが発火し、セッションクッキーが攻撃者のサーバーへ送信される。これだけでアカウント乗っ取りは完了だ。WAFを潜り抜けるような難読化も容易であり、この脆弱性は「致命傷」になり得る。
---
2. 対策の鉄則:DOMPurify 以外の選択肢はない
「自分で正規表現を書いてタグを除去する」といった甘い考えは今すぐ捨ててくれ。HTMLのパースは、人類が解決するにはあまりに複雑すぎる。
現場で唯一信頼できるのは、DOMPurify だ。これはHTMLをパースし、安全なタグと属性のみを残してクリーンアップする、いわば「DOMの検疫所」だ。
実装サンプル:Vue.js でのセキュアな実装
まずは npm install dompurify でインストールしてくれ。その上で、コンポーネント内では以下のように扱うのが「現場の正解」だ。
import DOMPurify from 'dompurify';
export default {
props: {
rawHtml: {
type: String,
required: true
}
},
computed: {
// テンプレート側で直接関数を呼ぶとレンダリングのたびに走るため、
// computedでキャッシュさせるのがパフォーマンスの定石だ
sanitizedHtml() {
// DOMPurify.sanitizeで悪意あるスクリプトを根こそぎ取り除く
// 設定オプションで許可するタグや属性をホワイトリスト化できる
return DOMPurify.sanitize(this.rawHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'], // 必要最小限に絞る
ALLOWED_ATTR: ['href', 'title', 'target']
});
}
}
};
テンプレート側はこう書く。
---
3. 防御は多層で:CSP(Content Security Policy)の適用
アプリ側の対策で完璧を目指すのは素晴らしいが、万が一のミスを想定するのがプロの流儀だ。Nginxやサーバー側で CSP (Content Security Policy) を設定し、インラインスクリプトの実行を物理的に禁止しておくべきだ。
例えば、Nginxの設定(あるいはメタタグ)に以下を追加する。
CSPのヘッダー設定例
'unsafe-inline' を許可しないことで、XSSの攻撃成功率を劇的に下げる
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none';";
これにより、たとえ v-html 経由で悪意あるタグが挿入されても、ブラウザが「このスクリプトは信頼できないから実行しない」と拒絶してくれる。これは最後の砦だ。
---
最後に:エンジニアとしての矜持
「動けばいい」というコードは、数ヶ月後の自分やチームを苦しめる負債になる。v-html を使うときは、心の中で「これは劇薬を扱っている」と唱えてほしい。
1. まずは Mustache {{ }} が使えないか検討する。
2. どうしてもHTMLが必要なら、必ず DOMPurify を通す。
3. CSPで外部からの攻撃を遮断する。
セキュリティは「魔法の杖」があるわけではなく、こうした地味な積み重ねが、組織をインシデントから守る唯一の道だ。今日から君のプロジェクトを見直し、v-html が野放しになっていないか確認してくれ。それが、プロとして仕事をするということだ。
コメント