FlawOpen

flawopen.com/JavaScriptのXSS/Vue v-html

Vue v-htmlのXSSリスクと安全な使い方

リファレンスページ
やさしい説明

Vueの {{ comment }} は、メモをガラス越しに見せるようなものです。山かっこも文字として表示されます。v-html はブラウザーにメモをHTMLとして開くよう指示します。ユーザーが制御するマークアップなら、ブラウザーがページ構造や有効なコンテンツとして扱い、XSSリスクにつながる可能性があります。

危険 — ユーザー由来の生HTML
<p v-html="comment"></p>
// commentは実際のマークアップとして解釈される
修正 — サニタイズ済みリッチHTML
<script setup>
import { computed } from 'vue'
import DOMPurify from 'dompurify'

const props = defineProps({ comment: String })
const safeComment = computed(() =>
  DOMPurify.sanitize(props.comment ?? '', {
    ALLOWED_TAGS: ['p', 'strong', 'em'],
    ALLOWED_ATTR: []
  })
)
</script>

<template>
  <div v-html="safeComment"></div>
</template>

v-htmlの前にリッチHTMLをサニタイズ

内容がテキストならVueの {{ }} 補間を優先してください。ユーザー作成のリッチHTMLが本当に必要な機能では、DOMPurifyなど保守されているライブラリを狭い許可リストで使い、サニタイズ後の値だけを v-html に渡します。サニタイザーを更新し、許可するマークアップをテストしてください。サニタイズはリスクを減らしますが、あらゆるHTMLをすべての状況で安全にするわけではありません。アクティブなコンテンツを信頼できない状態で扱う必要があるなら、分離したiframeを使います。Vueは v-html の内容をVueテンプレートとしてコンパイルしません。

v-htmlを監査してデータフローを追跡

すべての v-html バインディングを検索します。各箇所で、値の出所、サニタイザーと設定、描画内容を見るユーザーを確認します。ユーザー入力、保存済みコメント、CMS項目、API応答を確認し、通常のテキストには補間が使われているか確認します。

grep -rn "v-html" --include="*.vue" .

よくある質問

ReactのdangerouslySetInnerHTMLと同じリスクですか?

どちらも通常のテキストエスケープを迂回してHTMLをDOMに挿入します。リッチHTMLが必要なら保守されているサニタイザーを使い、サニタイズ結果があらゆる状況で安全だとは考えないでください。

参考資料

関連項目 クロスサイトスクリプティング(XSS、CWE-79)の仕組みと各言語での防ぎ方 — flawopen.com WKWebView loadHTMLString は未信頼コンテンツに安全? 反射型・格納型・DOM ベース XSS の違いは? Android addJavascriptInterface のセキュリティリスク Angular の XSS 対策:安全な既定動作とバイパス API MIME sniffing XSS:Content-Type と nosniff CSP と出力エスケープ:両方必要ですか?