Vue.jsを使うときはDOM-based XSSに注意しなければなりません.例えば,v-htmlがXSSの原因となる可能性があることは有名です.
Dynamically rendering arbitrary HTML on your website can be very dangerous because it can easily lead to XSS attacks. Only use v-html on trusted content and never on user-provided content. *1
v-bindとJSONを組み合わせることで,同様にXSSの原因となる場合があることを紹介します.
v-bind
v-bindを引数なしで使うと,Vue.jsは指定したObjectに従ってルート要素のattributeを生成します.
<!-- binding an object of attributes --> <div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
つまり,指定したObjectにユーザーの入力が含まれる場合はXSSの原因となります.
解決方法
inheritAttrsをfalseにすれば,ルート要素にattributeが追加されなくなります.
簡単なXSSのサンプル
goodParagraph.vueはinheritAttrsをfalseに指定したコンポーネントで,badParagraph.vueは初期設定のままのコンポーネントです.それ以外は全く同じです.これらに同じmessage変数をv-bindで指定してレンダリングしてみます.
App.vue
<template> <div id="app"> <good-paragraph v-bind="message" /> <bad-paragraph v-bind="message" /> </div> </template> <script> import GoodParagraph from "./components/goodParagraph.vue"; import BadParagraph from "./components/badParagraph.vue"; export default { name: "App", components: { GoodParagraph, BadParagraph, }, data() { return { message: { text: "body", onClick: "javascript:alert(1);", }, }; }, }; </script>
goodParagraph.vue
<template>
<div>
<h1>GoodParagraph</h1>
<p v-text="text" />
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
text: String,
},
};
</script>
badParagraph.vue
<template>
<div>
<h1>BadParagraph</h1>
<p v-text="text" />
</div>
</template>
<script>
export default {
inheritAttrs: true,
props: {
text: String,
},
};
</script>
実行結果
<div id="app"> <div> <h1>GoodParagraph</h1> <p>body</p> </div> <div onclick="javascript:alert(1);"> <h1>BadParagraph</h1> <p>body</p> </div> </div>
上記の例では,badParagraph.vue
にonclick attributeを追加できていますが,inheritAttrsをfalseにすることで,XSSが回避できていることが分かります.
このサンプルは以下のGitHubレポジトリに公開しています.