XSS-labs靶场通关指南:从原理到实战的20关Web安全进阶
2026/6/24 18:54:00 网站建设 项目流程

1. 项目概述:为什么选择XSS-labs作为你的第一块磨刀石?

如果你刚接触Web安全,或者对跨站脚本攻击(XSS)这个概念还停留在“听说过”的阶段,那么找一个好的靶场来练手,是比看一百篇理论文章都更有效的学习方法。在众多靶场中,XSS-labs以其清晰的关卡设计、由浅入深的难度梯度和对XSS核心原理的精准覆盖,成为了无数安全新手的“启蒙老师”。这个靶场模拟了20种不同的场景,每一关都像一道精心设计的谜题,你需要利用对HTML、JavaScript以及浏览器解析逻辑的理解,去找到那个“注入点”,并成功执行任意脚本。

我最初接触它时,也走了不少弯路,比如过分依赖自动化工具,或者死记硬背Payload而不知其所以然。后来才发现,XSS-labs的精髓不在于“通关”,而在于迫使你去思考:代码是如何被嵌入的?浏览器是如何解析和渲染的?过滤规则是如何被绕过的?这20关,几乎涵盖了反射型、存储型、DOM型XSS中最常见的触发场景和绕过技巧。通过手动攻克每一关,你能建立起对XSS漏洞最直观的肌肉记忆。今天,我就结合自己多次通关的经验,以及带新人时他们最容易卡壳的地方,为你拆解这20关的突破思路。我们的目标不是给出标准答案,而是让你理解每一关背后的“为什么”,从而具备独立分析和构造Payload的能力。

2. 核心思路与前置知识:磨刀不误砍柴工

在开始“闯关”之前,我们必须统一思想,明确XSS攻击的本质和通关的核心方法论。盲目尝试只会事倍功半。

2.1 XSS攻击的核心逻辑与三种类型

XSS的全称是Cross-Site Scripting,为了和CSS区分而简称XSS。其核心攻击逻辑是:攻击者将恶意脚本代码注入到可信的网页中,当其他用户浏览该网页时,嵌入的恶意代码会被浏览器执行,从而在用户不知情的情况下窃取Cookie、会话令牌,甚至进行钓鱼、挂马等操作。

根据恶意代码的存储和触发位置,主要分为三类:

  1. 反射型XSS:恶意脚本来自当前HTTP请求。最常见的是,攻击者构造一个包含恶意代码的URL,诱骗用户点击。服务器接收到请求后,未经过滤便将恶意代码“反射”回用户的浏览器页面中执行。XSS-labs的前面大部分关卡都是这种类型。
  2. 存储型XSS:恶意脚本被永久存储在服务器端(如数据库、文件系统)。当其他用户访问某个页面(如论坛帖子、评论列表)时,服务器从存储中取出并返回恶意脚本,导致其在用户浏览器执行。危害更大,因为所有访问者都可能中招。
  3. DOM型XSS:漏洞的根源在于前端的JavaScript代码。攻击者通过修改页面的DOM(文档对象模型)环境,诱使客户端脚本执行非预期的操作。整个过程不涉及与服务器的交互(恶意代码不经过服务器),完全在浏览器端完成。

XSS-labs的关卡设计巧妙地将这三种类型的原理融入其中,你需要根据页面反馈和代码逻辑来判断属于哪一种,并采取相应的攻击策略。

2.2 通关必备的工具与浏览器设置

工欲善其事,必先利其器。以下是我实战中离不开的几样东西:

  1. 浏览器开发者工具:这是你最重要的“眼睛”。主要使用两个面板:
    • 元素面板:查看页面渲染后的HTML结构。当你注入的代码被插入后,可以在这里看到它最终是如何被呈现的。特别注意<script>标签是否被创建、属性值是否被闭合。
    • 网络面板:查看浏览器发送的HTTP请求和接收的响应。对于反射型XSS,你可以在这里清晰地看到你输入的Payload是如何被发送,以及服务器返回了什么。这对于分析过滤和编码规则至关重要。
  2. Burp Suite 或 HackBar 浏览器插件:用于方便地构造和发送HTTP请求。Burp Suite功能强大,可以拦截、修改、重放请求,是专业测试的标配。对于新手,浏览器插件版的HackBar更为轻量快捷,可以直接在浏览器地址栏下方构造GET/POST请求,非常适合XSS-labs这类靶场练习。
  3. 一个简单的HTTP服务器:用于接收被窃取的数据(如Cookie)。当你的Payload成功执行后,通常需要证明其危害性,比如将用户的Cookie发送到你的服务器。你可以用Python快速搭建一个:python3 -m http.server 8000。这样,所有发送到http://你的IP:8000/的请求都会被记录。
  4. 浏览器安全策略调整:为了更清晰地观察漏洞效果,有时需要暂时调整浏览器设置。例如,在Chrome中,可以启动时加上参数--disable-xss-auditor(旧版本)或注意现代Chrome的XSS Auditor已被移除,主要依赖CSP。练习时,也可以暂时禁用CSP(内容安全策略),但这仅限本地靶场环境,真实测试中绝对不可行。

注意:所有工具和技巧仅用于授权的安全测试和学习环境(如本地搭建的XSS-labs)。未经授权对他人的网站进行测试是非法行为。

2.3 通用测试流程与思维模型

面对每一关,建议遵循以下步骤,形成条件反射:

  1. 信息收集:正常输入一串特征明显的测试字符串,如<test>。提交后,立刻用开发者工具的“元素面板”查看这个字符串被放在了HTML的什么位置。是被放在标签内(如<div>你的输入</div>),还是标签的属性里(如<input value=“你的输入”>),或者是JavaScript代码段中(如<script>var a = ‘你的输入';</script>)?位置决定了你的攻击方式。
  2. 上下文分析:确定输入点所处的“上下文”。是HTML正文?是HTML标签属性(单引号还是双引号包裹)?是JavaScript字符串?还是URL地址?不同的上下文,需要不同的闭合和绕过方式。
  3. 尝试闭合与构造:根据上下文,尝试用></script>等符号去闭合当前的语法环境,为插入恶意脚本(如<script>alert(1)</script>onerror=alert(1))腾出空间。
  4. 观察过滤与编码:如果简单的Payload失败了,查看网络面板的响应或页面源码,看你的输入哪些部分被修改了。是被删除了?被编码了(如<变成&lt;)?还是被替换了?根据过滤规则思考绕过方法。
  5. 利用事件与协议:当<script>标签被过滤时,就要转向利用HTML标签的事件属性(如onmouseover,onload,onerror)或支持JavaScript协议的属性(如<a href=javascript:alert(1)><img src=1 onerror=alert(1)>)。

3. 前10关:基础语法闭合与事件触发

这10关是基本功训练,核心是理解HTML的语法结构和如何闭合它们。

3.1 第1-3关:直接的标签注入与简单属性闭合

第1关:通常是最简单的,没有任何过滤。你的输入直接出现在HTML正文中。你只需要构造一个完整的<script>标签即可。例如,在输入框提交:<script>alert(document.domain)</script>。这里用document.domain代替简单的数字,可以更直观地证明你控制了当前域下的脚本执行。

第2关:输入被放在了HTML标签的属性值里,比如一个<input>标签的value属性。查看元素会发现类似:<input type=“text” value=“你输入的内容”>。这里的难点是你被关在双引号里面。你需要先闭合双引号,然后引入事件处理器或新标签。Payload:“><script>alert(1)</script>。这个Payload的分解是:

  • “>:先闭合当前属性值的双引号,再闭合<input>标签本身。
  • <script>alert(1)</script>:然后在原标签后面插入新的脚本标签。 这样,最终的HTML就变成了:<input type=“text” value=“”><script>alert(1)</script>“>。注意最后多了一个孤立的“>,但不影响脚本执行。

第3关:情况与第2关类似,但属性值是用单引号包裹的。查看元素:<input type=‘text’ value=‘你输入的内容’>。这时你需要用单引号来闭合。Payload:‘><script>alert(1)</script>。原理同上。

实操心得:很多新手在这里会困惑,为什么有时候要用“>,有时候用’>。秘诀就是看源码!永远不要猜,用开发者工具看一眼输入值被什么符号包裹着,你就用什么符号去闭合。这是最基本也是最关键的一步。

3.4 第4-6关:利用事件处理器与伪协议

<script>标签被过滤或上下文不适合插入完整标签时,我们就需要利用其他方式触发JavaScript执行。

第4关:输入可能被放在诸如<input><button>value属性中,且<>可能被过滤。这时,我们不必闭合标签,而是利用该标签本身支持的事件属性。例如,如果输入点在一个<input>标签内,我们可以尝试构造:“ onmouseover=“alert(1)。注意这里有一个空格。这个Payload的意图是:

  • :闭合前面的双引号。
  • 空格:分隔属性。
  • onmouseover=“alert(1):添加一个新的onmouseover事件属性,其值为alert(1)。由于我们没有闭合最后的双引号,它会“借用”原来标签结束的引号。最终生成:<input ... value=“” onmouseover=“alert(1)”>。当鼠标滑过这个输入框时,就会触发弹窗。

第5关:可能会过滤onmouseover这类常见事件,但可能漏掉一些不那么常见的事件,或者过滤了script关键字但没过滤javascript:伪协议。我们可以尝试使用<a>标签:“><a href=“javascript:alert(1)”>click</a>。或者,如果输入点在<script>标签的某个字符串变量中,你需要用</script>先闭合前面的<script>标签,再插入新的。

第6关:通常开始引入大小写绕过。如果系统过滤了<script>,但只是进行简单的大小写敏感匹配,那么<ScRiPt>就可能绕过。Payload:<ScRiPt>alert(1)</ScRiPt>。这一关是让你建立“过滤规则可能不完善”的意识。

3.5 第7-10关:双写绕过与编码初探

从这里开始,靶场会引入更积极的过滤机制,你需要学会“欺骗”过滤器。

第7关:可能会直接删除script这个关键词。例如,你输入<script>alert(1)</script>,服务器处理后变成了<>alert(1)</>。对付这种“删除”策略,一个经典的方法是双写绕过。Payload:<scrscriptipt>alert(1)</scrscriptipt>。当过滤器删除中间的script后,剩下的字符正好又组合成了一个新的<script>标签。

第8关:可能会将输入内容作为某个标签的属性值,并过滤空格和javascript:关键字。例如,你需要在一个<a>标签的href属性中注入。如果空格被过滤,你可以用Tab的URL编码%09或者换行符的编码来分隔属性。Payload:javascript:alert(1)可能被过滤,但可以尝试使用HTML实体编码进行混淆。例如,将javascript:编码为&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;:。但要注意,浏览器在解析href属性时会对HTML实体进行解码。所以你需要确保编码后的字符串在浏览器端能被正确解码为javascript:

第9关:可能会检查输入内容是否包含http://,要求看起来像一个合法的链接。这时你需要将恶意代码“伪装”成一个合法链接。Payload:javascript:alert(1)//http://xxx.com//在JavaScript中是单行注释,它会让后面的http://被注释掉,从而不影响前面的javascript:协议执行。

第10关:通常是一个综合练习,可能隐藏了多个输入点,或者需要通过查看网页源码发现隐藏的<input>表单。你需要利用前面积累的所有技巧,可能结合type=“hidden”的输入框,通过修改其valuetype属性,或者利用<form>onsubmit事件来触发XSS。这一关的关键是全面的信息收集,不要只盯着显眼的输入框。

4. 中阶挑战(第11-15关):DOM型XSS与闭合技巧进阶

从第11关开始,XSS-labs往往会引入更多DOM操作和复杂的上下文环境,挑战你对JavaScript和浏览器解析顺序的理解。

4.1 第11关:Referer头注入与HTTP头XSS

这一关通常不通过页面表单输入,而是通过修改HTTP请求头来触发XSS。常见的是Referer头或User-Agent头。服务器可能会将这些头信息未经处理就直接输出到页面的某个部分(比如一段注释或某个JavaScript变量中)。

攻击步骤

  1. 使用Burp Suite或浏览器插件拦截你对目标页面的请求。
  2. 在拦截的请求中,找到Referer头(或其他指定的头字段)。
  3. 将其值修改为你的XSS Payload,例如:Referer: “><script>alert(document.domain)</script>
  4. 放行请求,观察页面是否弹窗。

注意事项:HTTP头注入的XSS属于反射型,但触发方式更隐蔽。它要求攻击者能诱骗用户通过一个可控的代理发出请求,或者结合其他漏洞(如CRLF注入)来实现。在实战中,检查应用是否将不可信的数据从HTTP头回显到页面,是一个重要的测试点。

4.2 第12关:User-Agent头注入与过滤绕过

原理与第11关类似,但可能对<script>等关键词进行了过滤。你需要尝试使用其他标签和事件。例如,将User-Agent头设置为:“ onfocus=“alert(1) autofocus=“。这里构造了一个自动获取焦点的元素,当页面加载时,onfocus事件会自动触发。或者使用<img src=1 onerror=alert(1)>。关键在于观察服务器对头的处理方式,是直接嵌入HTML还是先做了处理。

4.3 第13关:Cookie注入与输入点挖掘

这一关可能会检查HTTP请求中的Cookie值,并将其输出。攻击方式与头注入类似,但Cookie通常由服务端设置,客户端直接修改可能不会生效(因为服务端可能不信任客户端传来的Cookie)。但有些设计不当的应用,可能会根据请求中的某个Cookie参数来动态生成页面内容。你需要仔细分析页面逻辑,或者查看源码,寻找类似document.cookie或服务器端模板变量输出Cookie的地方。

技巧:如果Cookie值被输出到<script>标签内的一个字符串变量中,如var userInput = ‘你的cookie值’;,那么你需要先闭合单引号,然后插入代码。Payload:‘;alert(1);//。这样会得到var userInput = ‘’;alert(1);//’;//注释掉了后面的内容。

4.4 第14关:DOM XSS - 利用document.write

DOM型XSS的典型场景。页面中可能有一段JavaScript代码,从URL的片段标识符(#后面的部分)或查询参数中获取数据,然后通过document.write()innerHTML直接写入页面。

例如,源码中可能有:

var input = window.location.hash.substring(1); document.write(“<p>你输入的是:” + input + “</p>“);

攻击者可以构造URL:http://靶场地址/level14.html#<img src=1 onerror=alert(1)>。当受害者访问此URL时,window.location.hash的值是#<img src=1 onerror=alert(1)>substring(1)去掉#后,剩下的字符串被直接拼接进HTML并写入文档,导致<img>标签被解析,其onerror事件触发。

核心思路:找到那些从location.searchlocation.hashdocument.referrer等来源获取数据,并直接传递给innerHTMLouterHTMLdocument.write()eval()等“危险”函数的代码点。

4.5 第15关:DOM XSS - 利用eval或innerHTML

这一关是第14关的变种或强化。可能使用eval()函数,或者通过innerHTML赋值,并且可能对输入内容进行了一些编码或过滤。

例如:

var data = decodeURIComponent(window.location.search.split(‘=’)[1]); document.getElementById(‘someDiv’).innerHTML = data;

这里从URL参数获取值,进行URL解码后,直接设置innerHTML。你可以构造:?data=<img src=1 onerror=alert(1)>。如果参数值被URL编码了,你可能需要输入编码后的形式,或者依赖浏览器/服务器的自动解码。

绕过技巧:如果过滤了<>,可以尝试使用JavaScript模板字符串或者通过String.fromCharCode动态生成标签。例如,<img可以写成<img,但注意在innerHTML上下文中,浏览器会解析实体,所以有时需要双重编码,或者寻找不依赖尖括号的注入方式,如利用已有标签的属性。

5. 高阶绕过(第16-20关):编码、特殊上下文与综合技巧

最后几关模拟了现实中更严格的过滤环境,需要综合运用多种绕过技术。

5.1 第16关:利用HTML实体编码与JavaScript Unicode编码

这一关可能会对输入中的特殊字符进行HTML实体编码,比如将<转成&lt;,将>转成&gt;。如果这种编码发生在服务端,并且你的输入被放在HTML标签属性值中,那么<script>标签就无法直接使用了。

但是,如果输入被放在<script>标签内部的字符串中,情况就不同了。例如:

<script> var x = ‘用户输入’; // 后续可能用eval(x)或document.write(x) </script>

如果服务器对用户输入进行了HTML实体编码,变成了&#60;script&#62;,那么在<script>标签内,它只是一个字符串,不会被浏览器解析为HTML标签。然而,如果这个字符串后来被传递给eval()或者作为innerHTML的值,那么问题就来了。eval()执行的是JavaScript字符串,而innerHTML赋值时浏览器会解析HTML实体。

攻击思路

  1. 寻找二次输出点:也许编码后的字符串会被输出到另一个不编码的上下文。
  2. 利用事件处理器:即使尖括号被编码,在HTML属性中,事件处理器如onmouseover仍然可以工作,只要它所在的标签本身是存在的。例如,如果输入被放在一个<input>value里,且被编码,你无法闭合标签。但如果你能注入一个空格和事件属性,如onmouseover=alert(1),即使后面的值被编码,只要属性名onmouseover被正确识别,它依然可能执行。但前提是属性名本身没有被过滤或编码。
  3. 使用JavaScript Unicode编码:在<script>标签内部,你可以使用\u0061\u006c\u0065\u0072\u0074(1)来表示alert(1)。如果服务器只过滤了关键词但允许Unicode字符,这可能有效。

5.2 第17关:SVG标签与事件绕过

当常规的HTML标签和事件被严格过滤时,可以尝试使用SVG(可缩放矢量图形)标签。SVG是XML格式,内嵌在HTML中,并且支持HTML的事件处理器。

一个典型的SVG XSS Payload:

<svg onload=alert(1)>

或者更复杂一点:

<svg><script>alert(1)</script></svg>

有些过滤器可能只针对常见的<img><div>等HTML标签,而忽略了<svg>。此外,SVG内部可以包含<script>标签,这提供了另一种执行脚本的途径。

5.3 第18关:利用Flash/ActionScript(历史技巧)或HTML5新特性

这是一关比较有历史感的关卡,可能涉及已淘汰的技术,但其思路仍有借鉴意义。早期有些XSS通过嵌入恶意的Flash文件(.swf)并利用ActionScript的ExternalInterface.call()来调用页面JavaScript。如今Flash已被淘汰,但思路可以转换。

现代绕过可能包括:

  • <details>标签的ontoggle事件<details ontoggle=alert(1) open>open属性使其默认展开,触发ontoggle
  • <video><audio>标签的onplay事件:结合autoplay属性。
  • <body>标签的onpageshow事件<body onpageshow=alert(1)>
  • 利用<iframe>srcdoc属性<iframe srcdoc=“<script>alert(1)</script>“></iframe>srcdoc属性内的内容会作为一个独立的文档解析,可能绕过一些针对当前文档的过滤。

5.4 第19关:基于CSS的XSS(极少见但存在)

XSS不一定非要执行JavaScript,理论上能控制样式表(CSS)也可能造成危害(如窃取数据),但难度极高。更常见的是利用CSS表达式(IE特有,已废弃)或style标签的属性。但现代浏览器中,纯CSS很难直接执行JS。

一种可能的思路是,如果输入被放入<style>标签内或元素的style属性中,且过滤不严,可以尝试注入expression(...)(仅旧版IE)或者利用@import引入外部资源,但这通常无法直接执行脚本。这一关更可能是误导,或者考察你是否能想到非常规的向量。在实际测试中,应优先关注能直接执行脚本的上下文。

5.5 第20关:综合审计与代码分析

最后一关通常不会引入新的技术,而是模拟一个小型、完整的应用程序片段,其中包含多个潜在的输入点和复杂的客户端逻辑。你需要像真正的代码审计一样:

  1. 静态分析:仔细阅读前端JavaScript代码,追踪所有用户可控数据的来源(location.*,document.*,window.name,postMessage等)。
  2. 动态调试:使用浏览器开发者工具的“源代码”面板,在可能的危险函数(eval,setTimeout,setInterval,Function constructor,innerHTML,outerHTML,document.write,location.assign,location.replace等)上设置断点。
  3. 数据流追踪:观察用户输入是如何经过各种字符串处理函数(replace,substring,decodeURIComponent,escape等)的,过滤规则是否存在逻辑漏洞(如只过滤一次、大小写问题、顺序问题)。
  4. 多步骤利用:可能需要结合两个或多个输入点,或者利用一个点的输出作为另一个点的输入,形成链式利用。

例如,代码可能先从URL参数#data获取值,经过一次replace(‘<’, ‘’)过滤,然后赋值给一个变量。这个变量又被另一个函数使用,该函数用innerHTML输出。这里过滤只删除了第一个<,你可以用<<script>来绕过,删除第一个<后,剩下<script>

6. 实战后的思考:从靶场到真实世界

通关XSS-labs只是第一步,它帮你建立了对XSS漏洞原理和基础绕过手法的直觉。但在真实的渗透测试或代码审计中,情况要复杂得多。

6.1 自动化探测与手动验证

在实战中,我们通常会先用自动化工具(如Burp Suite的Active Scan, 或专门的XSS扫描器)进行初步的爬取和测试。这些工具能快速发现明显的、标准化的漏洞。但是,高价值的、需要绕过的XSS漏洞,几乎100%依赖于手动测试。自动化工具生成的Payload往往很直接,遇到过滤就容易失败。你需要:

  • 分析过滤逻辑:通过输入<test>“’等测试串,观察返回结果,判断是黑名单过滤、编码、还是删除。
  • Fuzzing测试:使用Burp Intruder或自定义脚本,对关键参数进行模糊测试,尝试大量特殊的字符和Payload变体,观察哪些被拦截,哪些能通过。
  • 上下文切换:同一个参数,在GET、POST、Cookie、Header中,服务端的处理逻辑可能不同。多尝试几种请求方式。

6.2 绕过WAF(Web应用防火墙)的常见思路

企业级应用通常部署了WAF,它们有更强大的规则集。绕过WAF需要更多的技巧和耐心:

  1. 混淆与编码
    • HTML实体编码<变成&lt;,但注意解码时机。
    • URL编码<变成%3C>变成%3E。有时双重编码(%253C)可能有效。
    • Unicode编码<变成\u003c(在JavaScript字符串中)。
    • Base64编码:结合data:协议使用,如<img src=“data:image/svg+xml;base64,PHN2ZyBvbmxvYWQ9YWxlcnQoMSk+“>(这是<svg onload=alert(1)>的base64编码)。
  2. 拆分与拼接:利用JavaScript的字符串拼接能力。如<script>eval(‘al’+’ert(1)‘)</script>
  3. 利用冷门标签与事件:WAF规则可能覆盖不全。尝试<svg>,<math>,<details>,<audio>等标签,以及onbegin,onend,oncanplay等不常见事件。
  4. 协议与伪协议:除了javascript:,旧版浏览器还支持vbscript:data:协议也非常强大。
  5. 请求方式变换:将GET请求改为POST,或者将参数放在JSON body中,有时能绕过基于URL和参数名的检测规则。

6.3 防御视角:如何写出安全的代码

作为开发者,理解攻击手法是为了更好地防御。根本的防御原则是:“一切用户输入皆不可信”

  1. 输入验证:在服务端对输入进行严格的、白名单式的格式验证(例如,邮箱地址就只允许特定字符)。但验证不能替代输出编码。
  2. 输出编码:这是防御XSS最有效的手段。根据数据输出的上下文,进行正确的编码:
    • 输出到HTML正文:使用HTML实体编码(& -> &amp;,< -> &lt;,> -> &gt;,” -> &quot;,’ -> &#x27;)。
    • 输出到HTML属性:同上,并且属性值一定要用引号括起来。
    • 输出到JavaScript:使用JavaScript Unicode编码(\uXXXX)。
    • 输出到URL:使用URL编码。
    • 现代前端框架:如React, Vue, Angular等,默认提供了安全的输出机制(如React的JSX会自动转义),但也要警惕使用dangerouslySetInnerHTMLv-html等危险API。
  3. 使用CSP:内容安全策略是一个强大的深度防御措施。通过HTTP头Content-Security-Policy,你可以告诉浏览器只允许加载来自特定源的脚本、样式、图片等。即使攻击者注入了脚本,如果源不在白名单内,浏览器也不会执行。例如:Content-Security-Policy: default-src ‘self’; script-src ‘self’ https://trusted.cdn.com;
  4. 设置HttpOnly Cookie:为会话Cookie设置HttpOnly标志,可以阻止JavaScript通过document.cookie访问,从而缓解Cookie窃取攻击。
  5. 避免危险函数:在代码审计中,警惕直接使用innerHTML,outerHTML,document.write(),eval(),setTimeout()/setInterval()(第一个参数为字符串时),new Function()等。如果必须使用,必须对输入进行严格的净化和编码。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询