最近学习了vue.js环境下xss攻击场景相关的知识。整理出来分享给大家。
常见的vue.js数据绑定是通过双大括号{{}}或者v-text指令进行的数据绑定的。vue.js会自动将对应的模版编译成js代码。
例如:模版代码为
<p v-text="message"></p>
vue.js会把这段模版代码编译成:
function anonymous() { with(this) { return _c('p', { domProps: { "textContent": _s(message) } }, []) } }
一般情况下,vue.js通过双大括号向html内容中对指定部分进行插值。
而双大括号{{}}和v-text指令上底层上实现主要都是通过元素 DOM属性的textContent值来实现数据插入。
这种情况下一般不会出现注入的问题,因为浏览器的原生api会安全的处理这些值。 但是数据插值有时候需要向页面中插入富文本信息。如果使用{{}} 和 v-text指令的话,富文本会被转义成普通文本。不能实现需求。
这时候可能需要用到v-html标签。v-html标签的模版在vue.js编译的形式如下:
示例模版:
<p v-html="message"></p>
vue模版渲染代码:
function anonymous() { with(this) { return _c('p', { domProps: { "innerHTML": _s(message) } }, []) } }
可以看的v-html标签底层实际上使用了 Dom元素的innerHTML属性进行数据插入。而 innerHTML属性在被插入不信任数据的时候就会导致JS注入的问题。
安全防御:日常vue.js中数据绑定应该尽量使用{{}}和v-text的方式。v-html是一个有风险的指令,使用时一定要对数据进行过滤处理。
标签属性绑定,其实也是数据绑定的一种。我把它独立出来是因为属性上的数据绑定和上一小街其实的区别还是很大。
web前端开发中除了数据内容填充,同时也需要对标签的属性进行数据绑定操作。
vue中我们使用v-bind 标签属性进行数据绑定,这些数据的插值也是通过浏览器原生api对数据进行转码插入。由于浏览器自身api安全的保障,数据插入一般不会造成闭合属性导致的xss问题。
示例模版:对a标签的href进行数据插入
<a v-bind:href="1234"></a>
vue.js编译后的代码:
function anonymous() { with(this) { return _c('a', { attrs: { "href": 1234 } }, []) } }
这种场景下,往往是开发人员安全意识缺失,在对标签属性进行数据绑定的时候,忽略对于某些标签的危险属性绑定时的特殊处理。
我们知道,特殊标签的某些属性,可以在属性值可控的情况下进行js代码注入。这样又回到了通用场景下 xss防护应该要注意的问题。
web危险属性大致有:
所有元素的style属性。(应避免用户输入数据绑定到标签的style属性中,防范钓鱼攻击。)
a 标签的 href 属性。(正常情况应保证url 协议是http 和 https)
iframe 标签的 src 属性。(要防止通过 javascript:// 执行js)
object 标签的 data 属性 。(要防止通过 javascript:// 执行js.)
form 的 action 属性(要防止通过 javascript:// 执行js.)
防护知识:对于src 、href 、action 、 data 这类可以赋值为uri的属性要限制好协议和请求的url,尽量保证使用http:// 和 https:// 协议及访问可信的资源服务器 。
其实不仅仅是vue,在目前前后端分离开发的趋势下,前端Javascript框架如果使用了服务端模版渲染模版的方式,就都有可能出现服务端模版XSS。(这类XSS的本质是模版注入,并不一定出现都在服务端渲染场景)。
服务端渲染模版本身是为了提前生成html,利于某些站点的seo 和加快页面加载,如果对模版中用户数据处理不当,就会导致模版注入的问题。
举例:
如果你在vue.js开发的网站中输入了{{ 2+2 }},后端服务器给返回了4的时候。你可以断定这里存在模版解析的问题(服务端将用户输入直接放到template 中作为template的一部分解析返回)。
那么,如何攻击vue.js模版注入漏洞?
但在vue组件模版中,我们不能直接调用底层javascript函数。利用vue的模版注入一般使用的是javascript的prototype的特性。
首先我们知道,vue会把模版解析成js代码。那么对于模版注入漏洞,我们只需要将注入数据在解析结果里变成一段想要的js代码就可以了。
Javascript 中的一切内容都是对象。每个对象都包含一个protoptype (原型),prototype其中有个属性叫constructor ,它指向的是该对象构造函数。
函数也是一个对象,函数对象的constructor是一个允许动态生成函数的函数。这个constructor只要简单把代码字符串赋值给它就可以构造一个匿名的JS 函数。
举个例子:利用toString函数的构造函数,通过赋值构造一个匿名函数。
toString.constructor("consolo.log(1)")();
调用这个匿名函数:
Vue 的模版注入payload 原理就就是这样,常用的测试payload有:
{{constructor.constructor('alert(1)')()}} {{_c.constructor('alert(1)')()}} {{_v.constructor('alert(1)')()}} {{_s.constructor('alert(1)')()}}
还有很多payload , 大家可以到参考资料中的xss-cheat-sheet中查找。
防护知识:对于模版注入,我们可以通过v-pre指令对模版中的用户输入进行处理。v-pre 指令可以跳过指定点的模版编译。将起用在用户输入上可以防止用户输入造成的模版注入问题。
关于vue.js XSS 相关内容的分享就到这里。其他的攻击点就靠大家一起发掘啦。
参考文档:
http://www.mabiji.com/vuejs/vue-directive.html
https://template-explorer.vuejs.org/#
https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#vuejs-reflected
https://portswigger.net/research/evading-defences-using-vuejs-script-gadgets
本文作者:天玑@涂鸦智能安全团队
漏洞悬赏计划:涂鸦智能安全响应中心(https://src.tuya.com)欢迎白帽子来探索。
招聘内推计划:涵盖安全开发、安全测试、代码审计、安全合规等所有方面的岗位,简历投递[email protected],请注明来源。