☰
跨站脚本攻击XSS实战靶场通关指南:从基础payload到高级绕过
2026/10/17 4:23:00 网站建设 项目流程

1. 项目概述与设计思路

1.1 靶场定位:为什么我推荐从专项靶场开始练XSS

如果你准备入门Web安全,想找一个能把跨站脚本攻击(XSS)练明白的地方,XSS-Labs是一个绕不开的专项练习靶场。它和综合性的漏洞练习平台不一样,整个靶场只干一件事:把XSS的常见攻击场景浓缩成一系列由浅入深的关卡,从最简单的反射型弹窗,一路演到编码绕过、DOM型payload、存储型覆盖,每一关都对应一个真实业务里常见的过滤姿势。你不需要像打综合靶场那样先花半天摸索环境,打开页面、看到参数、找到输出点,就可以开始动手。

我最初接触这个靶场时,已经能背出基础payload(攻击载荷),比如<script>alert(1)</script>、<img src=x onerror=alert(1)>之类的,但在真实目标上总是碰壁。后来刷完这套专项练习,才意识到一个问题:XSS不是“背几个标签”就能搞定的,它考验的是你对浏览器解析机制的理解,以及对输出点上下文(HTML标签内、属性内、脚本块内、URL内)的敏感度。这个靶场把这些能力拆成了一关一关的台阶,每过一关,你对“过滤规则长什么样、怎么绕过、为什么能绕过”就多一层体感。

所以这篇文章适合三类人:刚入门Web安全、想系统梳理XSS知识的学生或爱好者;已经在其他靶场乱枪打鸟、但总感觉没形成方法论的新手;以及需要快速给团队做安全培训、拿一套现成案例来讲原理的开发者。内容会覆盖核心思路、具体通关案例、常见问题排查三方面,尽量做到“看完能自己再刷一遍”。

1.2 通透的关卡递进:从反射型到DOM型的路线规划

理解了靶场整体结构,你就能预判自己会学到什么。常见的关卡序列在逻辑上分为三个阶梯:

第一阶梯是纯反射型XSS。服务器把用户传入的参数直接拼进HTML响应,然后原路返回给浏览器。这里的考点是:参数被拼进了哪些位置,以及服务器有没有做基本的过滤。你会发现,同样是反射型,拼在标签外、属性里、事件值中,payload写法完全不同。

第二阶梯开始加入过滤规则,比如把script、onerror、alert等关键字直接替换成空、转换成大写、或者用实体编码转义。这一阶段训练的是“语法级绕过”:既然完整标签被过滤,就改用标签拆分;既然alert被过滤,就用prompt或confirm;既然尖括号被编码,就尝试属性注入和事件触发的旁路。

第三阶梯是DOM型和存储型。DOM型的输入不经过服务端转发,而是在前端JavaScript里通过location.search、document.write、innerHTML等接口被消费,肉眼在响应包里看不到payload,必须捋脚本执行链。存储型则要求你把payload写入数据库,等管理员或其他用户访问时触发,这已经接近真实攻击链的第一步。

这些关卡的设计思想,本质上就是从“看到输出点”到“推理输出点”的转变。靶场刻意没有把所有输出点都标出来,就是为了逼你在源码里找注入点——这恰恰是实战里最值钱的技能。

2. 关键武器与核心机制拆解

2.1 浏览器解析XSS的三层递进:HTML、URL与JavaScript

在动手过关之前,先要把XSS触发的基本机制盘顺。几乎所有payload能生效,都离不开浏览器对页面内容的解析顺序:先解析HTML结构,再识别其中的URL和脚本。这三层解析按顺序执行,而绕过思路往往就藏在这层与层之间的空档里。

举个例子,你提交<script>alert(1)</script>,本质上是让服务器把这段字符串放进了HTML响应的某个位置,浏览器解析出这是HTML标签,于是会去执行标签内的JavaScript代码。但很多过滤机制处理的是字符串本身,而不是解析结果。于是就有了经典的绕过逻辑:既然过滤规则不认识编码后的形态,那就在字符串里把字符编码,让浏览器解析时还原成原本的标签或关键字。

具体到三个层面:

  • HTML实体编码:比如&lt;script&gt;,过滤规则如果只匹配文本层面的<script>就发现不了,但浏览器解析HTML后,会把它还原成真正的<script>标签并按照HTML语义解析执行。
  • URL编码:javascript:alert(1)如果出现在href属性里,浏览器会在解析URL时进行URL解码。比如写成javascript&#58;alert(1)或者jAvAsCrIpT:...,针对的正是只做字符串匹配、不做二次解码的过滤规则。
  • JavaScript字符串编码:脚本内容里如果用了八进制、十六进制或Unicode转义来写关键字,比如\x61lert,JavaScript引擎真正执行时才会解码,又会绕过一层文本匹配。

很多新手过不了难关,是因为只在一个层面里尝试替换字符。实际调试时我习惯先在浏览器开发者工具里看一眼最终渲染的DOM树,确认过滤后的payload被解析成了什么结构,再决定下一步怎么构造。

2.2 常用payload速查与适用场景

刷靶场时没必要每次从头构造payload,这里先放一份高频实用的速查表,后面关卡案例会反复用到:

场景Payload示例适用条件
直接注入HTML标签<script>alert(1)</script>输出点不在标签属性内,且<、>未被过滤
属性内注入"><script>alert(1)</script>输出点在双引号包裹的属性值内
单引号属性注入' onfocus='alert(1)或' autofocus onfocus='alert(1)输出点在单引号包裹的属性值内
事件触发<img src=x onerror=alert(1)>script被过滤但标签未被完全过滤
伪协议触发<a href="javascript:alert(1)">点我</a>输出点在href或src属性,且允许javascript:协议
字符编码绕过<img src=x onerror=&#97;&#108;&#101;&#114;&#116;(1)>过滤了关键字但没做实体解码
大小写混淆<sCrIpT>alert(1)</sCrIpT>过滤规则对关键字做了严格大小写匹配
DOM型payload#<img src=x onerror=alert(1)>前端使用innerHTML、document.write且未进行过滤

注意:实战中不要对真实业务系统直接用这些payload,靶场和授权测试环境才是练习的唯一正确场所。

2.3 通关需要的环境与工具

这个靶场的环境依赖很简单:一个能跑PHP的环境。本地建议搭一套PHP内置服务器环境,配置好端口映射即可。我自己常开的容器里直接启用PHP模块,访问根目录就能看到关卡入口。

辅助工具方面,浏览器开发者工具是必须的,重点用三个面板:

  • 元素面板:查看渲染后的DOM树,确认payload被转化成什么结构。很多肉眼看不到的解析结果,在这个面板都会原形毕露。
  • 网络面板:查看HTTP请求与响应原文,确认payload到达服务器后有没有被过滤,以及服务器返回了什么内容。
  • 控制台:观察JavaScript执行报错和日志,排查DOM型关卡很有用。

有条件的话,可以准备一套抓包工具,在需要精确修改请求参数时用。比如某个关卡依赖GET参数,但你想用POST方式探测,或者需要浏览器的URL编码干扰payload本身,抓包工具会更直观。不过绝大多数关卡用浏览器自带的开发者工具就够了,不必过度配置。

3. 实战通关:反射型关卡的核心案例

3.1 第一关:直接拼接,无过滤的反射型

关卡入口一般会给一个带name参数的链接,比如?name=Kobe。这一步先不要急着输入payload,而是打开元素面板,观察一下“Kobe”这个值被放到了页面的哪个位置。你会发现它被直接拼在HTML正文里,前后没有引号包裹,也没有任何过滤动作。这种最理想的条件,直接用一发入魂的payload就能过关:

?name=<script>alert(1)</script>

这里想提醒一个细节:payload不只是“能弹窗”,而是要确认你的JavaScript代码执行力在哪一层。第一关之所以经典,是因为它在整个靶场里唯一一次给你无过滤的完整HTML上下文,之后每一关都会在代码或转义上做手脚。所以你可以顺手试几个变体,比如<img src=x onerror=alert(1)>,感受一下不同载荷的触发时机差异:script标签是页面解析到就执行,img的onerror则依赖图片加载失败这个事件。

3.2 第二关:双引号属性内的逃逸

到了第二关,页面上会有个搜索框,你输入的内容最终会被放进value="..."属性里。这里有一个典型的误区:新手直接提交<script>alert(1)</script>,发现没有反应,就以为靶场有全局过滤。其实只是输出点被包在双引号属性值里,你输入的双引号在HTML解析时并不会终止属性值。

正确思路是先闭合属性,再创建新的事件属性或标签。先提交一个单引号或双引号,看页面源码有没有变化,再构造:

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

双引号闭合了value属性,>退出标签,后面的<script>就在标签外,自然会被当作新标签解析。这一关练的是属性注入的基本功,后续很多关卡都是在它的基础上加过滤条件。

3.3 第三至第六关:单引号、事件属性与关键字过滤的绕行

越往后越要养成“按输出点位置分情况”的习惯。三至六关看起来是升级版的反射型,实际上分别对应了四种写法:

单引号属性注入。源码里用了'包裹属性值。此时双引号逃逸失效,你需要用单引号闭合,同时考虑绕过引号过滤。典型思路是构造一个不依赖新属性标签也能触发的payload。比如:

' onfocus=alert(1) autofocus '

单引号闭合掉原本的属性值,onfocus和autofocus作为新属性存在,当输入框自动获得焦点时触发事件。这一类payload的通用价值很大,因为很多过滤规则会挡掉<>,但防不住纯属性的自闭合链路。

事件属性型。过滤规则把script标签整个屏蔽了,但没限制其他标签的事件属性。此时使用<img>、<svg>或<body>这类标签配合onerror、onload、onfocus等事件即可。我在这一阶段会刻意测试各种标签和事件的组合,因为onerror虽然好用,但某些过滤会把它替换成空值,此时换成onload或者onmouseover就能绕过。

关键字大小写混淆。过滤规则判定是否包含script关键字,但是用的是精确匹配且不忽略大小写。于是<ScRiPt>能正常解析执行。这类关卡通常训练的是对过滤逻辑的敏感性——先看它过滤了哪个词,再想哪个形态能绕过去。

引号与实体混用。源码做了实体编码,双引号和尖括号被转义。此时单纯的闭合思路失效,需要判断是不是能利用浏览器二次解码的特性,或者寻找那些没有经过实体编码的输出点。这类题往往需要结合响应原文逐字排查,也是很多新手第一次卡住的地方。

说句实在话,这些关卡间有大量重复套路,但每一次重复都能加深“先看渲染结果,再决定payload”的条件反射。我经常看到有人不看响应就狂刷payload,那不是过关,是在撞运气。

4. 高阶绕过:编码、DOM与存储型关卡的实现细节

4.1 编码绕过的底层逻辑:当过滤、解码与解析三权分立

通过前几关之后,你会遇到过滤条件明显变严的场景。它们共同的特征是:后台做了关键字替换或转义,但只做了一层,或者只做了文本层面。这正是XSS绕过的经典战场。

比如服务器把alert替换为空字符串,但只替换一次。你可以构造<script>alert(1)</script>没法直接通过,于是做成<script>alalertert(1)</script>,过滤逻辑会把中间的alert替换掉,留下的恰好是完整的alert。这种“插入折叠”的绕过逻辑在各类过滤场景里都能复用。

再比如服务器使用了类似于把<>转成实体编码的防护,但没有处理已经编码过的输入。你可以提交<img src=x onerror=alert(1)>,但如果输出点允许HTML实体解码,那就再用实体编码表达尖括号和关键关键字。构造示意(注意这里的字符序列在浏览器二次解析才会还原):

<imgsrc=x onerror=&#97;&#108;&#101;&#114;&#116;(1)>

发动机理其实不复杂:过滤是基于输入字符串的文本匹配,而浏览器解析是基于解析后的渲染树。文本匹配发生在最外层,浏览器解码发生在渲染层,两者处理的是不同的信息载体。只要找到一个“过滤处理不到、但浏览器会还原”的层级,绕过就成立。我在这一步开始大量使用网络面板反复刷新,确认原始响应里有没有被新的编码方式还原成为有效的标签和函数。

4.2 宽字节与中文编码类歧义

还有一种比较经典的绕过,是在后端用反斜杠转义引号时,利用数据库或页面的字符编码歧义来实现逃逸。典型情况大致是这样:后台对输入里的单引号转义,在前面加一个反斜杠,期望输出\'让引号失去语义。但如果系统将输入按某种宽字节编码解析,又对输出按另一种编码处理,就会产生字符消耗的错位。

举个例子,构造%bf%27,%27是单引号的URL编码,%bf是某个字符的首字节。如果后台用了GBK等宽字节编码,\(%5c)与%bf%27里的%bf拼接后会被解析成一个完整中文字符,剩下的%27就还原为单引号,而前面的反斜杠被“吃掉”了,从而闭合属性成功。这个技巧不一定在每套靶场里都有对应关卡,但你理解这个原理之后,遇到类似Web应用时可排查范围会大很多。

值得特别注意的是,现在大多数现代框架的数据库连接和页面编码都统一成了UTF-8,这类攻击的生存空间已经缩小,但在老旧系统和历史项目里依然存在。过关时如果技术环境允许,可以单独拿一个PHP环境来复现这种歧义,对理解“编码是Web安全里的显式边界”非常有帮助。

4.3 DOM型关卡:看前端JavaScript才是主战场

DOM型的XSS和前面所有反射型关卡都有一个关键区别:payload不一定会出现在HTTP响应原文里。你提交的碎片可能只是拼进了一个URL的哈希片段,之后前端脚本读取这个片段,再把不可见文本塞进document.write或innerHTML里。

在DOM型关卡中,我强烈建议打开开发者工具的调试器,沿着“输入来源→数据流→消费函数”这条路径读代码。找出类似下面这个模式的代码:

var name = new URLSearchParams(window.location.search).get('name'); document.getElementById('result').innerHTML = "欢迎 " + name;

这里的输入会最终进入innerHTML,浏览器会把name当作HTML字符串解析,而不是纯文本。于是你提交任何HTML标签都会被执行。但如果你只提交<script>alert(1)</script>却没有反应,很可能是innerHTML的解析机制不会执行脚本标签(浏览器对innerHTML插入的script有特殊处理),需要改用<img src=x onerror=alert(1)>这类事件型payload。这也是DOM型关卡反复要调的细节,顺着脚本读数据流才能看到他设计的“消费点”到底把输入当成了HTML还是文本。

我实战里刷DOM型关卡最有价值的一个习惯,是每次过关后主动反问自己:“如果这里的输入源不是URL参数而是location.hash,payload会有什么区别?”答案是哈希片段不会发送到服务器,抓包时根本看不到,排查难度会陡增。这也是为什么DOM型在真实场景中尤其需要前端代码审计能力的原因。

4.4 存储型关卡:让payload留存在服务端

存储型XSS的触发链比反射型多了一步:payload被持久化存进数据库,任何用户访问包含这段数据的页面都会触发。靶场里通常的表现是,有一个“留言板”或者“个人资料”功能,你提交的内容会被保存,然后当你(或模拟的其他用户)刷新页面时再次被渲染出来。

存储型关卡的常见难度差异在于:保存过程和展示过程的过滤可能不同。我曾经调试过一个存放了多级过滤条件的环境,提交时后台删掉了script标签,展示时前端再用textContent渲染,八字还没一撇。但另一个环境保存时不严格,展示时只做了引号转义——结果我在保存时插入<img src=x onerror=alert(1)>,展示时原样拼入页面,成功触发。这说明存储型XSS必须同时考虑保存侧与展示侧两套过滤逻辑,这也是为什么它在边界防御上更难搞。

存储型还有一个衍生考点:储存在不同位置的payload,触发条件可能完全不同。有的在首页被渲染,有的在文章详情页才拼接,有的只在管理后台显示。后台展示意味着你甚至可以拿一类管理员的会话环境做试验——当然这属于授权的渗透测试场景,靶场里没有真实后台系统,但理解这个思路就够了。

5. 常见问题排查与通关避坑指南

5.1 典型问题速查:为什么我的payload不生效

刷靶场时最让人沮丧的不是不会,而是“明明看了答案却还是不通”。这里列表整理我实际调试中遇到的高频坑:

症状可能原因排查方向
payload在页面源码中显示为纯文本输出点被textContent渲染而非innerHTML检查前端渲染函数,改用事件型载荷
页面源码中看到<script>但没弹窗innerHTML插入的script不会自动执行换成img+onerror或svg+onload
单引号被转义成\'后台做了字符转义检查后端是否统一转义单引号,考虑宽字节或事件属性注入
双引号被转义成&quot;输出做了HTML实体编码看是否还有未编码的标签层,尝试伪协议或DOM型
过滤了关键字但没过滤标签关键词黑名单机制尝试拆解关键字、大小写混用、实体编码
过滤了script但过滤后删空简单替换为空的过滤逻辑使用“嵌套折叠”技巧,如写成scrscriptipt
请求在Burp里改得挺好,但浏览器不执行浏览器自动URL编码干扰payload关掉自动编码,或用抓包工具直接发送

5.2 排查方法论:三步定位过滤规则

光有速查表还不够,我更想分享的是排查的思路顺序,这也是我反复踩坑后总结出的三层定位法:

第一步,看响应。打开网络面板,查看服务器实际返回的HTML片段里,你的输入被改成了什么样。这是最顶层的真实证据,能帮你立刻判断过滤发生在哪一层,而不是猜。

第二步,看渲染。打开元素面板,看浏览器解析后的DOM树里payload变成了什么。如果文本敲进去了,但DOM树里没出现对应标签结构,说明HTML解析阶段就把它当纯文本了。

第三步,看执行。打开控制台和调试器,看JavaScript有没有执行、有没有报错。有时payload在DOM树里已经“正确”了,但因为浏览器的安全策略或脚本错误导致没有触发。这一步能帮你区分“没有装上”和“装上没点火”。

这套方法几乎适用于所有关卡。我遇到过很多次,明明payload已经在响应里完美呈现,但就是不弹窗,排查到后来才发现是innerHTML对script的不执行策略——而这一步不进入“看执行”层面根本发现不了。

5.3 通关路线总结与个人经验补遗

如果你是按顺序刷的,复盘时可以把通关过程画成一张自己的能力地图:无过滤的场景你用什么策略;属性内逃逸你有没有形成肌肉记忆;遇到过滤器你优先看的是响应还是先改payload;DOM型关卡你是否自觉去找前端源码。这些都比单纯“刷完”更有含金量。

我个人实测的体会是,这套靶场最大的价值不在“通关”本身,而在于它把XSS的各种变体强制性地重复了几十遍,帮你把“检查输出→确定上下文→构造载荷→验证触发”这个流程内化成习惯。很多人到后几关才发现,自己已经不再背payload了,而是看到代码结构脑子里自然浮现出绕过方案。这种能力比记住一百个payload都值钱。

最后补一个小技巧:碰见任何看起来卡死的关卡,先在元素面板里右键查看源码,把服务器返回的原始片段完整读一遍,再动手构造。绝大多数问题都出在“没看原始响应就开始猜过滤”上。靶场如此,真实代码审计更是如此。

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

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

立即咨询