咱們首先來看一個例子 javascript
運行以後因爲src地址對應的資源找不到,會觸發img標籤的error事件,最終alert彈框。這即是一個最簡單的xss攻擊。v-html指令源碼html
// /vue/src/platforms/web/compiler/directives/html.js
export default function html (el: ASTElement, dir: ASTDirective) {
if (dir.value) {
addProp(el, 'innerHTML', `_s(${dir.value})`)
}
}
複製代碼
編譯生成的渲染函數是 前端
運行時在created階段觸發invokeCreateHooks函數 進而執行updateDOMProps函數,更新元素的innerHTML內容。能夠看到v-html指令最終調用的是innerHTML方法將指令的value插入到對應的元素裏。這就是形成xss攻擊的‘漏洞’了。固然vue官網也給出了友好提示vue
只在可信內容上使用 v-html,永不用在用戶提交的內容上。java
細心的同窗應該能發現v-html指令和咱們日常自定義的指令在渲染函數上有些不一樣的地方。咱們定義一個自定義指令v-test,而後在模板上使用。node
<div v-test="test"></div>
複製代碼
最終生成的渲染函數是 web
運行時一樣在created階段觸發invokeCreateHooks函數 進而執行updateDirectives函數。 再執行_update函數更新指令 最終在callHook$1函數中調用了咱們自定義指令的函數體。經過上面的分析能夠看出,二者在編譯階段的處理不相同,形成運行時html指令是不須要運行時函數的,而自定義指令是須要運行時函數來執行的。npm
編譯vue單文件組件是經過vue-loader來完成,其中編譯template部分是經過vue-loader/lib/loaders/templateLoader.js來完成。 架構
能夠看到編譯器使用的vue-template-compiler。另外vue-loader提供一個選項compilerOptions來指定編譯器的 配置。最終聚合成finalOptions傳給compileTemplate函數。該函數是@vue/component-compiler-utils包提供的方法,用來將模板字符串編譯成渲染函數。compileTemplate函數內部調用actuallyCompile函數,首先進行了選項的合併,最終執行compile函數來編譯。 compile函數最終又回到了vue-template-compiler/build.js 在調用baseCompile進行編譯前,作了指令的合併。將咱們以前從vue-loader傳入的compilerOptions.directives和baseOptions.directives進行了合併。 在baseCompile函數中,會經過以下調用鏈最終調用到genDirectives函數,來生成指令的代碼。 在這部分中,咱們以html爲例var gen = state.directives[dir.name]; // 有bind,cloak,modal,on,html,text
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el, dir, state.warn);
}
複製代碼
gen變量指向的就是html指令的定義。執行完gen(el, dir, state.warn)後,因爲html指令的定義中沒有返回值,因此needRuntime爲false。dom
從這裏咱們能夠看出指令是分爲兩類的,一類是編譯階段轉換爲一個模板 AST 的結點進行處理的,運行時就不須要執行指令函數。另外一類是運行時調用定義進行處理。
咱們從vue源碼的目錄結構劃分也能確認這兩類指令
一般咱們處理xss攻擊會使用一個xss的npm包來過濾xss攻擊代碼。因此咱們要作的就是給指令的value包上一層xss函數。有同窗可能會問,咱們在業務代碼裏使用xss函數處理也行。是的能夠,可是咱們不能保證團隊每個成員都會使用xss函數處理。做爲前端的架構師,咱們須要從項目總體的考慮來處理這類問題,不能期望經過規範來約束團隊成員。 有了前面的知識儲備,咱們知道了在編譯前會將咱們從vue-loader傳入的compilerOptions.directives和baseOptions.directives進行了合併。 這樣咱們就能覆蓋html指令。
1.引入xss包並掛載到vue原型上
import xss from 'xss';
Vue.prototype.xss = xss
複製代碼
2.在vue.config.js中覆寫html指令
chainWebpack: config => {
config.module
.rule("vue")
.use("vue-loader")
.loader("vue-loader")
.tap(options => {
options.compilerOptions.directives = {
html(node, directiveMeta) {
(node.props || (node.props = [])).push({
name: "innerHTML",
value: `xss(_s(${directiveMeta.value}))`
});
}
};
return options;
});
}
複製代碼
生成出來的渲染函數
渲染的真實dom這樣咱們就從源頭解決了html指令存在的潛在xss攻擊。