XSS-Labs通关秘籍:7种花式绕过技巧与实战解析
1. 项目概述:为什么XSS-Labs是Web安全进阶的必修课
如果你在安全圈待过一阵子,或者刚入门Web安全,大概率听说过“XSS-Labs”这个靶场。它不像DVWA那样面面俱到,也不像WebGoat那样有完整的课程体系,但它在XSS(跨站脚本攻击)这个细分领域,尤其是各种“花式绕过”技巧上,堪称一座宝库。我当年啃这个靶场的时候,好几次卡在某个关卡,对着过滤规则琢磨半天,最后灵光一闪找到绕过方法时,那种豁然开朗的感觉,比解出一道数学难题还爽。这个靶场的设计者,绝对是深谙各种WAF(Web应用防火墙)和过滤机制的老手,他把实战中可能遇到的过滤场景,抽象成了一道道关卡。
今天要聊的,就是一篇通关秘籍,标题叫《XSS-Labs通关秘籍:从AngularJS注入到ZeroClipboard漏洞的7种花式绕过》。这标题本身就信息量巨大,它点出了两个关键的高级攻击面:AngularJS注入和ZeroClipboard漏洞,并暗示了至少有7种不同的绕过思路。这可不是简单的<script>alert(1)</script>就能搞定的,它涉及前端框架的特性滥用、第三方库的漏洞利用、编码技巧、协议处理等多种维度。对于想从“脚本小子”进阶到真正理解XSS原理和绕过艺术的安全从业者、渗透测试工程师,甚至是前端开发人员(了解如何防御),这篇文章的内容都是极具价值的。它不仅仅是一份答案,更是一套思维体操,训练你如何站在防御者的角度思考,再以攻击者的视角寻找缝隙。
2. 核心思路拆解:理解靶场的“过滤哲学”
在开始具体的绕过技巧之前,我们必须先理解XSS-Labs靶场(或者说,大部分有效的过滤机制)的核心设计哲学。它的目标不是创造一个“铁桶阵”,而是模拟真实世界中不完美但常见的防御措施。这些防御通常基于“黑名单”或“简单正则匹配”,这就给绕过留下了空间。
2.1 常见过滤手段与思维定势
靶场常见的过滤包括但不限于:
- 关键词过滤:直接删除或转义
<script>,onerror=,javascript:等字符串。 - 事件处理器过滤:屏蔽
onclick,onmouseover,onload等HTML事件属性。 - 协议限制:检查
href或src属性是否以javascript:开头。 - 标签属性过滤:移除
href,src等敏感属性,或对属性值进行编码。 - 字符编码过滤:尝试解码用户输入一次,然后进行过滤,但可能未递归解码。
许多初学者容易陷入的思维定势是:过滤了<script>标签,XSS就没办法执行了。或者,过滤了onclick事件,就无法通过用户交互触发。高级绕过的精髓,就在于打破这些思维定势,寻找过滤逻辑的“盲区”和“边界条件”。
2.2 绕过技术分类框架
基于标题和常见的XSS绕过技术,我们可以将这“7种花式绕过”大致归为以下几类,这构成了我们后续分析的骨架:
- 框架特性滥用:如AngularJS的客户端模板注入。
- 第三方库漏洞利用:如ZeroClipboard这类Flash或JS库的历史漏洞。
- 编码与混淆艺术:利用HTML、JS、URL编码的多层嵌套与差异。
- 协议处理歧义:利用浏览器对
javascript:、data:等协议解析的微妙之处。 - 非常规事件与属性:使用不那么常见但同样有效的事件处理器或HTML属性。
- 跨上下文攻击:从一种注入上下文(如HTML属性)跳转到另一种(如脚本块)。
- 逻辑缺陷结合:结合应用本身的业务逻辑(如文件上传、解析功能)实现XSS。
3. 深度解析:AngularJS客户端模板注入(CSTI)
AngularJS(通常指1.x版本)在前端MVC时代风靡一时,但它引入的“双花括号”{{ }}模板语法,在不当使用时,会成为一个严重的客户端模板注入(Client-Side Template Injection, CSTI)漏洞。这严格来说不完全是传统意义上的XSS,但它能达到执行任意JavaScript代码的效果,是绕过许多传统过滤的利器。
3.1 漏洞原理与利用条件
AngularJS的模板引擎会在客户端(浏览器)解析{{ }}中的表达式。默认情况下,表达式在沙箱中执行,但这个沙箱在早期版本或特定配置下可以被绕过。最关键的是,当攻击者能够控制被AngularJS解析的数据绑定表达式时,漏洞就产生了。
例如,假设一个页面使用了AngularJS,并且有如下代码:
<div ng-app> <p>欢迎, {{ username }}!</p> </div>如果username这个变量用户可控(比如从URL参数?username=test读取),那么攻击者就可以注入恶意表达式。
利用条件:
- 页面引入了AngularJS库。
- 存在用户输入被直接拼接进AngularJS模板或
ng-*指令属性的地方。 - 没有使用
$sce(严格上下文转义)服务对不可信内容进行严格过滤。
3.2 经典Payload构造与绕过
在XSS-Labs的上下文中,关卡可能会过滤所有常见的HTML标签和事件,但往往忽略了AngularJS的模板语法。因为{{ }}看起来不像脚本,可能不在黑名单里。
基础Payload:
{{ 1+1 }} // 测试表达式是否被执行,页面会显示“2” {{ constructor.constructor('alert(1)')() }} // 一种常见的沙箱逃逸并执行代码的方式(在旧版本中有效)更高级的利用会涉及AngularJS的$scope对象。例如,通过$eval函数:
{{ $eval('alert(1)') }}或者利用$new函数创建新的作用域并执行代码。
实操心得: 在实际测试中,首先需要判断页面是否使用了AngularJS。查看页面源码,搜索ng-app、angular.js等关键字。确认后,寻找任何可能将你的输入输出到页面上的位置,尝试注入{{ 7*7 }}。如果页面显示了49,那么恭喜你,找到了注入点。接下来就是尝试构造能执行代码的Payload。注意,AngularJS不同版本(1.0.x, 1.2.x, 1.6.x)的沙箱限制和逃逸方法不同,需要针对性测试。在靶场环境中,通常设计为某个旧版本或特定配置,让经典的逃逸方法生效。
注意:现代Angular(2+)和严格配置的AngularJS 1.x(使用
$sce)极大地缓解了此问题。但在遗留系统或配置不当的应用中,这仍是一个高危漏洞。
4. 深度解析:ZeroClipboard漏洞的XSS利用
ZeroClipboard是一个用于实现“点击复制到剪贴板”功能的流行JavaScript库,它通过一个透明的Flash影片(.swf)来绕过浏览器对剪贴板访问的限制。正是这个Flash组件,历史上曾多次爆出XSS漏洞。
4.1 漏洞根源:Flash与JS的通信机制
ZeroClipboard的工作原理是:JavaScript库创建一个Flash对象(ZeroClipboard.swf),并通过ExternalInterface让Flash与页面JavaScript通信。当用户点击覆盖在目标元素上的透明Flash时,Flash触发动作,并回调JavaScript函数,将指定文本复制到剪贴板。
漏洞通常出现在两个方面:
- SWF文件自身未对传入参数做严格过滤:攻击者可能通过构造特殊的URL参数或
flashvars,将恶意脚本注入到Flash执行的上下文中,最终传递回JS执行。 - 库的配置或回调函数使用不当:开发者在配置ZeroClipboard时,如果使用了未经净化的用户输入作为
text(要复制的文本)或者回调函数的参数,也可能引入XSS。
例如,一个常见的错误用法:
// 假设要复制的文本来自用户输入 var userInput = getParameter('copyText'); // 来自URL的攻击者可控输入 ZeroClipboard.config({ swfPath: "ZeroClipboard.swf" }); var client = new ZeroClipboard(document.getElementById("copy-button")); client.on("copy", function(event) { event.clipboardData.setData("text/plain", userInput); // 这里可能直接注入 });如果userInput是"><script>alert(1)</script>,当复制操作触发相关的HTML更新时,就可能造成XSS。
4.2 在XSS-Labs中的利用场景
靶场可能会模拟一个使用了老旧、存在漏洞版本ZeroClipboard库的场景。攻击者需要:
- 识别组件:通过查看页面源码或网络请求,发现
ZeroClipboard.swf的加载或相关JS代码。 - 寻找注入点:检查哪些参数会传递给Flash或相关的JS配置函数。可能是URL中的
#参数、flashvars,或者是通过JSsetText方法设置的数据。 - 构造Payload:利用已知的CVE漏洞Payload,或者尝试注入HTML/JS代码到回调流程中。例如,某些版本的ZeroClipboard允许通过
ready事件或copy事件的数据对象进行注入。
一个历史漏洞的简化利用思路(以CVE-2015-1028为例): 攻击者可能控制一个传递给Flash的URL参数,如movie参数。通过构造如http://vulnerable.site/path/to/ZeroClipboard.swf?movie=\"))}catch(e){alert(1);}//这样的URL,诱使页面加载恶意构造的SWF路径(或参数),从而触发XSS。
排查技巧: 遇到感觉“毫无头绪”的关卡,不妨打开浏览器的开发者工具,查看“网络”选项卡,过滤.swf请求。或者查看所有加载的JS文件,搜索“ZeroClipboard”关键字。一旦确认存在,就去搜索该库对应版本的历史CVE和公开的漏洞利用代码(PoC)。在靶场环境中,这往往是解题的关键线索。
5. 编码与混淆的艺术:绕过字符过滤
这是XSS绕过中最基础也最考验创造力的部分。核心思想是:让恶意Payload在绕过过滤检查时是一种形态,而在浏览器解析执行时又是另一种形态。
5.1 多重编码与解码差异
WAF或过滤函数通常只进行一次解码,而浏览器可能会进行多次、多层解码。
案例:HTML实体编码绕过假设过滤函数会转义<和>,但我们可以输入它们的HTML实体编码:
<script>alert(1)</script>如果过滤函数愚蠢到只是简单匹配<script>字符串,它会被绕过。但关键是,服务器端如果先解码再过滤呢?更高级的玩法是“双层编码”:
- 原始Payload:
<img src=x onerror=alert(1)> - 第一次URL编码:
%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E - 第二次HTML实体编码(对
%等符号):<img src=x onerror=alert(1)>(这里简化表示) 如果服务器接收输入后,先进行了一次URL解码(得到第2步),然后进行HTML实体编码(得到第3步?逻辑需厘清),但过滤检查却是在中间某个环节,就可能出现绕过。实际上,更常见的场景是:
- 输入:
<img src=x onerror=alert(1)> - 服务器端过滤函数:发现
onerror,删除。 - 绕过:输入
<img src=x oonerrornerror=alert(1)>,过滤函数删除中间的onerror,剩下onerror=alert(1)。
但编码绕过的经典例子是利用JavaScript自身的解码能力:
<img src=x onerror=eval(String.fromCharCode(97,108,101,114,116,40,49,41))>这里,alert(1)被转换成了ASCII码数组,String.fromCharCode会在JS执行时动态解码。过滤系统很难直接识别出这是alert。
5.2 非常用标签与属性
当<script>、<img>、<svg>等常见标签被过滤时,可以尝试一些“冷门”但同样有效的HTML标签。
<details>标签的ontoggle事件:
<details open ontoggle=alert(1)>当details元素展开或收起时触发ontoggle。open属性使其默认展开,从而立即触发。
<video>/<audio>标签的oncanplay事件:
<video src=x oncanplay=alert(1)></video>当媒体文件可以开始播放时触发。src=x(一个不存在的资源)会快速失败,但仍可能触发事件。
<body>标签的onpageshow事件: 如果能在注入点控制整个<body>标签的属性(极少见,但某些富文本编辑器漏洞可能导致),onpageshow是一个不错的选择。
<input>标签的onfocus与autofocus组合:
<input autofocus onfocus=alert(1)>autofocus属性让输入框自动获得焦点,从而触发onfocus事件。这个组合在不需要用户交互的情况下就能触发XSS,非常有效。
实操心得: 准备一个自己的“XSS标签/事件备忘录”非常有用。除了上面这些,还有<marquee>,<iframe>,<embed>的各类事件,如onstart,onload,onerror等。在实战或打靶时,当常见标签被禁,就从这个备忘录里挑选冷门标签进行尝试。浏览器的兼容性也需要考虑,有些事件只在特定浏览器或版本下有效,但靶场环境通常是固定的。
6. 协议处理与伪协议绕过
这类绕过主要针对对src、href等属性值的过滤,特别是对javascript:伪协议的检查。
6.1 利用data:协议
data:协议可以将小型数据直接嵌入URL。它可以用来承载HTML或JavaScript代码。
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>这里,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==是<script>alert(1)</script>的base64编码。如果过滤只检查javascript:而忽略了data:,就能成功绕过。
变形:利用data:协议执行JS。
<iframe src="data:text/html,<script>alert(1)</script>"></iframe>但注意,现代浏览器对data:协议内嵌脚本的同源策略限制越来越严格,这种方式在某些上下文中可能失效。
6.2 利用空白字符与大小写变异
简单的WAF正则可能是/^javascript:/i。绕过方法:
- 加入换行或制表符:
java script:alert(1)或java%0ascript:alert(1)(URL编码的换行)。某些解析器在处理URL时可能会忽略这些空白字符。 - 利用浏览器自动补全:如果属性值以
//开头,浏览器可能会将其补全为当前页面的协议(http://或https://)。但更常见的是利用javascript:后跟换行和注释:
<a href="javascript: alert(1)//">click</a>在某些解析逻辑中,第一行检查通过了(javascript:),后续的换行和代码被当作属性值的一部分,最终浏览器却能正确执行。
6.3 利用其他伪协议
虽然javascript:是最常见的,但还有其他伪协议在某些历史上下文中有用,如vbscript:(IE)、data:(如前所述)、jar:等。在针对特定环境(如老旧内网系统)时值得一试。
7. 跨上下文攻击:从属性到代码
这是指在一个注入上下文(如HTML标签属性)中“跳出”,进入到另一个更有力的上下文(如JavaScript代码块)。
7.1 跳出属性值,闭合标签
这是最直接的方式。如果注入点在一个标签的属性值里,并且属性值没有被引号正确闭合,或者可以闭合,那么就可以提前结束属性,然后引入新的事件或标签。 假设服务端输出:<input type="text" value="【用户输入】">
- 过滤不严时,输入:
"><script>alert(1)</script>,最终生成:
<input type="text" value=""><script>alert(1)</script>">这样就成功从value属性值中跳出,插入了新的<script>标签。
7.2 在JavaScript字符串中跳出
如果用户输入被直接拼接到<script>标签内的JavaScript字符串中,就需要跳出字符串,执行代码。 假设服务端输出:<script>var message = '【用户输入】'; </script>
- 输入:
'; alert(1); //,最终生成:
<script>var message = ''; alert(1); //'; </script>这里,我们首先用单引号'闭合了字符串,然后加入了我们的代码alert(1);,最后用//注释掉后面原有的单引号和分号,避免语法错误。
更复杂的情况:如果服务端对引号进行了转义(\'),可以尝试利用转义字符本身来绕过。例如,输入\'; alert(1);//,服务端转义后变成\\'; alert(1);//。当JS解析时,\\被解释为一个反斜杠字符,然后后面的'就被解释为字符串的结束符,从而跳出。
7.3 DOM型XSS与eval/setTimeout/innerHTML
DOM型XSS的绕过更灵活,因为过滤可能发生在客户端JS代码中。常见危险函数是eval()、setTimeout()/setInterval()(第一个参数为字符串时)、以及innerHTML/outerHTML赋值。
eval():如果可控参数直接传入eval(),几乎可以为所欲为。但通常会有一些过滤。可以尝试编码、字符串拼接等方式绕过对关键词的检测。
输入:// 假设 userInput 可控 var code = "alert('" + userInput + "')"; eval(code);');alert(1);//,最终eval("alert('');alert(1);//')"),成功执行额外代码。setTimeout:setTimeout("alert('"+userInput+"')", 1000),绕过方式类似。innerHTML:直接将未净化的字符串赋值给innerHTML会导致HTML解析,从而可能执行脚本。如果过滤了<script>标签,可以尝试前面提到的<img onerror>、<svg onload>等事件处理器。
8. 结合逻辑缺陷与功能特性的高级绕过
这是最高阶的绕过,需要深入理解目标应用的功能逻辑。
8.1 文件上传与内容类型欺骗
如果靶场有一关涉及文件上传,并且对上传文件的XSS过滤较松,可以尝试上传一个包含恶意HTML/JS代码的文件(如.svg、.html,甚至通过修改文件头伪装成图片的.jpg文件)。然后,通过诱使用户访问这个上传文件的URL来触发XSS。
- SVG文件:SVG是XML格式,可以内嵌JavaScript。
<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"> <script type="text/javascript"> alert(1); </script> </svg> - HTML文件:直接上传一个完整的HTML文件。
- 图片马:上传一个真正的图片,但在文件末尾附加HTML/JS代码。如果服务器端检查不严(只检查文件头),并且该文件最终以
text/html的Content-Type被提供,那么浏览器就会解析其中的脚本。
8.2 解析差异:浏览器vs服务器
利用浏览器和服务器端解析HTML/JS的差异。例如,服务器端可能使用简单的正则或字符串匹配来过滤<script>,但浏览器在解析HTML时,标签名是不区分大小写的。<ScRiPt>、<SCRIPT>都可能被浏览器识别为脚本标签,但可能绕过服务器端的过滤。
另一个经典的例子是换行符处理。在HTML中,换行符在某些上下文中不影响解析。但在JavaScript字符串中,换行符需要转义。如果服务器端和客户端对输入中换行符的处理不一致,可能导致过滤被绕过。
8.3 利用字符集编码
如果页面指定了特殊的字符集(如UTF-7),并且用户输入未经正确转码就输出,可能导致XSS。UTF-7编码的XSS Payload看起来像一堆加号和数字,极难被基于UTF-8的正则过滤识别。例如,+ADw-script+AD4-alert(1)+ADw-/script+AD4-在UTF-7编码下会被解码为<script>alert(1)</script>。不过,现代浏览器默认已不再支持将UTF-7作为页面编码,此技巧主要适用于历史遗留系统。
9. 通关实战:系统性测试方法论
面对像XSS-Labs这样的综合性靶场,不能盲目尝试。需要建立一套系统的测试方法。
信息收集:
- 查看页面源码:寻找所有可能的输入点(参数、表单、Cookie、URL片段)。
- 分析网络请求:查看加载了哪些外部资源(JS库、Flash、JSONP接口)。
- 观察输出位置:你的输入被回显在页面的哪个位置?是在HTML标签内、属性值里、JavaScript字符串中、还是CSS或注释里?这决定了注入的上下文。
试探过滤规则:
- 输入无害探测:先输入一串独特的字符串,如
TEST123,查看输出位置和形式,确认是否被编码或截断。 - 触发词测试:逐步输入
<、>、'、"、()、onerror=、javascript:等,观察哪些字符被过滤(删除、转义、替换)。 - 尝试简单Payload:输入
<img src=x onerror=alert(1)>,看是否被拦截。如果被拦截,是弹窗被阻止,还是Payload被修改?
- 输入无害探测:先输入一串独特的字符串,如
选择绕过路径:
- 根据过滤情况,匹配前面提到的绕过类别。
- 如果过滤了
<script>和on事件,尝试非常规标签/事件(如<details ontoggle>)。 - 如果过滤了
javascript:,尝试**data:协议或编码混淆**。 - 如果发现引入了AngularJS等库,尝试框架特性滥用。
- 如果发现Flash组件,搜索第三方库漏洞。
- 如果输入出现在JS代码中,尝试跳出字符串/注释。
构造并迭代Payload:
- 从一个基本的绕过思路开始构造Payload。
- 使用浏览器的开发者工具“控制台”和“元素”面板,实时调试和观察Payload被插入后的DOM结构。
- 如果失败,分析原因:是语法错误?还是事件没触发?抑或是有更深层的过滤?
- 迭代修改Payload,可能结合多种技巧(如编码+非常规标签)。
自动化辅助:
- 对于大量重复的测试,可以编写简单的脚本(如Python配合requests库)来批量尝试Payload字典。
- 使用Burp Suite的Intruder模块,加载XSS Payload字典进行模糊测试。
常见问题与排查技巧实录:
| 问题现象 | 可能原因 | 排查思路与技巧 |
|---|---|---|
| Payload输入后,页面无变化,也无错误。 | 1. 输入被完全过滤或删除。 2. 输出点不在当前视图(如输出到隐藏域、JS变量)。 3. 需要特定交互才能触发。 | 1. 查看页面源码,搜索你的测试字符串,看是否被输出、编码或截断。 2. 查看网络请求,输入是否被发送到后端,响应是什么。 3. 尝试所有可能的用户交互:点击、鼠标悬停、焦点变化等。 |
| 弹窗被浏览器拦截。 | 现代浏览器对alert()等函数在非用户直接交互(如页面加载自动触发)的调用会进行拦截。 | 1. 将alert(1)改为console.log(1)或alert(document.domain),后者有时能绕过简单的检测。2. 尝试通过用户交互事件(如 onclick)来触发,而不是onload/onerror。3. 使用 prompt(1)或confirm(1)有时也能工作。 |
| 事件处理器被完整删除。 | WAF或过滤函数精准匹配并删除了onxxx=字符串。 | 1. 尝试在事件处理器名中插入换行、制表符或空字符(如onclick%00=)。2. 使用HTML实体编码事件名的一部分(如 onclick->onclick)。3. 尝试使用SVG事件或其他命名空间下的事件。 |
| 在JS字符串中,引号被转义。 | 输入中的引号被添加了反斜杠转义(\',\")。 | 1. 尝试输入反斜杠本身来“转义转义符”:\'-> 输入\\',服务端转义为\\\',JS解析时\\是反斜杠字符,\'是字面量的单引号,从而闭合字符串。2. 尝试不使用引号,用 String.fromCharCode或模板字符串(反引号)绕过。 |
使用了<svg>或<math>标签但无效。 | 可能注入点所在的HTML上下文不允许这些标签,或者浏览器安全策略限制。 | 1. 确认注入点是否在<body>内,而不是在<textarea>或<xmp>等原始文本元素中。2. 尝试更基础的标签,如 <img>、<iframe>。3. 查看控制台是否有CSP(内容安全策略)错误。 |
最后,攻克XSS-Labs这类靶场,最大的收获不是记住那几十个Payload,而是培养出一种“绕过思维”。你会开始习惯性地思考:“这里过滤了什么?为什么这么过滤?它的边界在哪里?有没有它没想到的地方?” 这种思维模式,无论是在渗透测试、代码审计还是安全开发中,都是无价的。每解一道题,就像和出题人进行一次思维博弈,这个过程本身,就是最好的学习。
