刚把 XSS-labs 装完刷完那阵子我突然觉得以前自己学 XSS 的方式特别傻——到处找在线测试平台或者对着别人文章里的 payload 一顿乱改压根不知道为什么能弹窗、为什么被拦。直到把这个 16 关的本地靶场完整过了一遍很多过滤绕过上下文的概念才真正串起来。XSS-labs 是一个很经典的 PHP 编写的 XSS 专项练习靶场总共 16 关从最基础的反射型输出一路考到大小写绕过、双写绕过、编码绕过、请求头注入、模板包含。它不依赖数据库也不需要复杂的环境一个 Web 服务器加 PHP 就能跑。对于刚接触 XSS、想把弹窗玩明白的人或者准备面试前想突击绕过思路的人来说它几乎是性价比最高的起步项目。本文就把我自己的安装过程和 16 关通关记录完整整理出来顺便说清楚每一步背后的判断逻辑而不是单纯丢 payload。1. 为什么我劝所有学Web安全的人第一站选XSS-labs很多人一说到练手靶场第一反应就是 DVWA 或者 Pikachu。这两个我也装过确实功能全、覆盖面广但恰恰因为全导致每个漏洞类型的题目数量都不多练起来有点蜻蜓点水。XSS-labs 不一样它只做一件事XSS。但这一件事做了 16 种变化。1.1 它不是大平台而是一张精心设计的闯关地图XSS-labs 打开之后就是一个关卡列表每一关对应一个 PHP 文件比如 level1.php、level2.php一直到 level16.php。每关的页面都很简单绝大多数关卡只有一个输入点你需要做的就是让这个输入点成功执行 JavaScript。关卡之间是严格递进的前几关完全不设防教你怎么在 HTML 各个位置插入脚本中间几关开始用字符串过滤模拟伪 WAF逼你思考大小写、双写、编码后面几关把注入点藏到 Referer、User-Agent、Cookie 这些请求头里光靠浏览器地址栏已经没法直接做实验了最后几关甚至涉及 AngularJS 模板包含、空格被过滤之类比较偏门的场景。这种一关一主题的设计比一个大而全的漏洞平台更适合建立知识框架。刷完以后你至少能回答一个问题服务器端做了字符串过滤时XSS 还有哪些常见的活路。1.2 和 DVWA、Pikachu 的定位差异靶场漏洞类型范围XSS 题目深度依赖适合阶段XSS-labs仅 XSS16 关逐层加深PHP ApacheXSS 专项入门DVWASQLi、XSS、CSRF、文件上传等每个模块几关分低中高三档PHP MySQLWeb 安全整体入门PikachuXSS、SQLi、RCE、SSRF 等每个模块若干关PHP MySQL综合漏洞练习如果你已经确定要主攻 XSS 或者想在面试前突击绕过技巧XSS-labs 的效率是最高的。但我不建议只刷它因为 XSS 还需要配合 Burp Suite 改包、了解浏览器编码差异这些周边技能这些我会在最后一章单独说。1.3 适合什么样的学习者我觉得有两类人最适合先刷 XSS-labs一类是刚看完 XSS 概念、知道scriptalert(1)/script能弹窗但不知道为什么能弹的人。另一类是已经刷过 SQL 注入、对闭合这个概念有感觉想横向迁移到 XSS 的人。如果你已经能熟练写各种复杂 payload那这套靶场对你来说可能偏简单但用来整理思路、查漏补缺依然值得快速过一遍。至少我刷完之后再看一些所谓XSS 被过滤的实战案例脑子里会自动浮现出那 16 关对应的绕过模型。2. 安装与运行验证从下载到浏览器弹出alert(1)安装 XSS-labs 的门槛其实很低低到什么程度呢它不需要数据库不需要额外扩展只要你的 Web 服务器能跑 PHP 就够了。但也正因为很多人习惯性按装完整环境的思路去操作反而会踩到一些不必要的坑。2.1 环境选型老版本 phpStudy 是省心之选我本机是 Windows所以直接用了 phpStudy。这里有个经验尽量选择 2018 年左右的老版本 phpStudy配套的 PHP 版本是 5.x兼容性最好。XSS-labs 本身是老项目源码里用到了一些老 PHP 时代的写法如果你用最新的 PHP 8.x 去跑轻则报 Deprecated 警告重则因为部分函数行为变化导致页面白屏。当然如果你机器上已经有现成的 PHP 环境比如 Laragon、XAMPP、宝塔面板也都行。核心要求就三点Apache 或 Nginx 能跑起来PHP 版本建议 5.6 或 7.x别上来就装 PHP 8端口别冲突尤其是 80 端口。2.2 安装步骤记录以 Windows phpStudy 老版本为例完整步骤如下安装 phpStudy启动后先看 Apache 状态灯是否为绿色。如果亮红通常是端口被占或者软件被杀毒拦截后面单独讲。去 GitHub 搜 xss-labs找 star 数比较多的仓库下载 zip 包。解压后把整个文件夹放到 phpStudy 的WWW目录下。注意检查解压后的目录结构确保访问路径是http://127.0.0.1/xss-labs/level1.php而不是多套了一层文件夹。回到 phpStudy确认 Apache 已经启动然后在浏览器里访问http://127.0.0.1/xss-labs/。如果能看到一个关卡列表页面说明基本环境没问题。打开第一关在 URL 后面传参数?namescriptalert(1)/script弹窗出现安装完成。2.3 我怎么验证环境真的没问题很多新手在这一步容易犯迷糊页面能打开但一传参数就 404 或者空白于是开始怀疑环境坏了。其实可以先做一个最简单的排除法先访问http://127.0.0.1/xss-labs/level1.php不带任何参数如果页面正常显示一个输入框说明 PHP 解析正常在浏览器地址栏输入http://127.0.0.1/xss-labs/level1.php?nametest看页面上是否回显 test如果回显正常再改成 URL 编码的script比如%3Cscript%3Ealert(1)%3C%2Fscript%3E看浏览器行为。这三步走下来环境有没有问题就一目了然了。最怕的是你连第一步都不验证直接开刷出问题了才回头查环境。2.4 安装失败的常见问题排查我安装时和后来帮朋友排查时遇到最多的是下面几种情况80 端口被占用。phpStudy 启动 Apache 时默认监听 80 端口如果你本机装了 IIS、Nginx 或者其他 IDE 内置服务器就容易冲突。解决办法是在 phpStudy 设置里把 Apache 端口改成 8080或者关掉占用端口的程序。PHP 版本不对导致页面报错。症状是页面能打开但大量 Warning 直接输出在页面顶部甚至整个页面排版错乱。这种情况优先把 PHP 版本切换到 5.x。如果你的 phpStudy 版本太新没有 5.x可以换一个老版本重装比手动配环境省事得多。目录层级不对导致找不到文件。这是一个特别蠢但特别常见的坑。解压之后文件夹名字可能是xss-labs-master里面又套了一层目录。你要确保level1.php就在WWW/xss-labs/下面访问路径和文件结构一一对应。MySQL 被莫名占用。XSS-labs 不需要 MySQL但 phpStudy 默认会启动 MySQL有时候 MySQL 启动失败会弹窗干扰你判断。遇到这种情况直接到 phpStudy 里把 MySQL 关掉只留 Apache 就行。3. 十六关通关记录每一关的绕过思路和payload备忘这一章是全文的重点。我会把 16 关分成五组每关给出我当时的测试思路、最终可用的 payload以及这个 payload 为什么能成立。先强调一个原则我的环境是老版本 phpStudy PHP 5.6如果你用的版本不同某些关卡的过滤规则可能有细微差异。遇到不弹窗的情况第一件事不是换 payload而是打开对应 PHP 文件直接看源码。3.1 第 1~4 关反射型 XSS 的四种问候方式前四关难度很低但我建议当成四种经典输出场景来学因为它们对应了 XSS 最核心的闭合思想。Level 1URL 参数直接输出无过滤。访问level1.php?namescriptalert(1)/script直接弹窗。这关的源码大致是echo 欢迎用户.$str;没有任何过滤和上下文包裹是最简单的反射型 XSS。Level 2输出在 input 的 value 属性里需要用引号逃逸。第二关源码把输入放进了input value你的输入这种场景。直接在 URL 里传script会发现不弹窗——不是被过滤了而是脚本被当成了 value 属性的文本。当时我的第一反应是先输入一个双引号看看原形。最终 payload 是scriptalert(1)/script前面的用来闭合 input 标签的 value 属性和整个 input 标签后面的script才能在 HTML 文档里独立存在。这行的核心是明白属性上下文和 HTML 上下文的区别。Level 3换成了单引号包裹但过滤了部分标签。第三关源码把 value 换成了单引号包裹input value$str。如果还拿双引号去闭合就不行因为引号不匹配。我试了几次最终用事件属性绕过 onfocusalert(1) autofocus前一个单引号逃逸出 value 属性接着注入onfocus事件autofocus保证页面加载时自动触发焦点最后一个用来闭合新注入的属性。这关的关键是当标签内嵌属性时不一定非要另起一个标签直接在原标签上注入事件属性也是一种思路。Level 4双引号闭合但尖括号被处理掉了。第四关开始有点伪过滤的意思了。它把输入里的和做了处理导致不能再插入新标签。但因为是双引号属性事件注入依然可行 onfocusalert(1) autofocus这四关连起来看其实就在讲一件事先看输入点落在什么上下文再决定是用另起炉灶还是用空格加事件属性就地触发。3.2 第 5~7 关过滤脚本标签时的大小写与双写从第五关开始靶场正式模拟开发者做了一点过滤的状态。这类题目的本质是让你逆着过滤规则去构造字符串。Level 5过滤了script但只过滤了这一种。这关把script关键字替换掉了。我最开始还是试标准 payload结果页面直接把 script 标签的原型打了出来。换成a hrefjavascript:alert(1)x/a就成功了。原因很简单开发者只堵死了script这条路径但javascript:伪协议可以通过a标签触发。XSS 永远不止一种触发方式这关是最好的例子。Level 6过滤规则变多但忽略了大小写。第六关加入了对href、src、on等关键字的过滤看着像模像样。我一开始按第五关的a href试直接被拦。后来想到一个最经典的绕过思路如果过滤是大小写敏感的那就混着写。ScRiPtalert(1)/ScRiPt弹窗成功。这关以后我养成了一个习惯遇到过滤先试大小写成本最低。Level 7过滤规则做了删除操作可以双写绕过。第七关的过滤方式很有意思——它不是替换成安全字符而是直接删除关键词。比如script会被删掉那么script删完就变成啥也执行不了。但如果我写成scrscriptiptalert(1)/script服务器把第一个script删掉后剩下的字符串重新拼在一起刚好又组成一个script。这有点像字符串拼接游戏开发者以为删掉危险词就安全了但删除操作本身可以被人为构造的中间文本抵消。3.3 第 8~9 关href 场景下的编码与伪协议这两关把注入点放到了链接属性里也是实战中很常见的场景用户输入被放入a href用户可控内容。Level 8过滤了javascript:伪协议关键字。第八关输入点在一个href属性里我一开始写 onclickalert(1)其实也能尝试但题目意图更偏向考验伪协议。直接填javascript:alert(1)会被过滤。我当时想到了一个经典方案——用 HTML 实体编码绕过关键字匹配javas#99;ript:alert(1)浏览器解析href属性时会先做 HTML 实体解码于是#99;被还原成字母c最终等价于javascript:alert(1)而服务端的字符串过滤只盯着原始文本看自然拦不住。Level 9不仅要能执行还要让链接里出现http://。第九关比第八关多了一个条件输入的链接必须包含http://字样否则不展示。这明显是为了防住只写javascript:的 payload。绕过方案是让http://出现在注释里javascript:alert(1)//http:////在 JavaScript 里是注释符后面那串http://只是为了让服务端检测通过实际执行时根本不会影响前面的alert(1)。这关给我最大的启发是过滤规则匹配的是字符串而浏览器执行的是语义——只要字符串表面上满足条件语义上照旧就能钻空子。3.4 第 10~13 关从隐藏参数和请求头里找注入点到这里浏览器地址栏直接改参数已经不够用了后面几关开始考验数据来源的意识。一个页面里的数据可能来自 URL、Referer、User-Agent、Cookie每一处都可能成为 XSS 的入口。Level 10注入点藏在隐藏表单字段里。第十关页面长得和前几关不太一样输入框变成了一个看起来不可控的隐藏字段。我一开始在参数名上做文章试了几次后发现它其实是把某个 GET 参数的值拼到了 hidden input 的某个属性里而且对参数值做了过滤。常规思路是让注入的字符串去闭合这个属性这种题建议直接打开源码。我看到源码里隐藏域的 name 是t_sort但具体过滤规则各版本不完全一样。关键是通过 URL 传一个t_sort参数让它的值以开头配合事件属性闭合绕开过滤去触发。这类藏字段的考点就是你以为页面没有输入点其实每个可控的参数名和参数值都可以是输入点。Level 11XSS 藏在 Referer 请求头里。第十一关把访问者的 Referer 直接回显到了页面某个属性里。浏览器地址栏没法改 Referer我开了 Burp Suite拦截请求后手动把 Referer 改成 onmouseoveralert(1)放行后页面加载时只要鼠标滑过注入点就会弹窗。这关的启发是做 XSS 测试不能只盯着 GET/POST 参数HTTP 头里的每一处回显都可能是漏洞点。Level 12User-Agent 成了下一个注入点。和第十一关玩法一致只是把注入点换成了 User-Agent。Burp 里改掉 UA 头即可。我写的是 onfocusalert(1) autofocusUA 头是浏览器每次请求默认带上的如果服务端没过滤直接输出就等于是个隐形的反射型 XSS。这个场景在真实世界里要比你想象的常见——某些防护设备记录的日志页面、管理后台的访问记录展示页都是重灾区。Level 13Cookie 里的注入。第十三关把 Cookie 中的某个值直接输出到了页面上。我在 Burp 里给请求加了一个自定义 Cookie 字段值是 onmouseoveralert(1)思路和 11、12 一模一样。这三关放在一起刷最大的收获是建立了所有可控数据都可能成为注入源的意识同时也熟悉了 Burp 改包的基本操作。3.5 第 14~16 关几个不按常理出牌的关卡最后三关难度陡增而且网上很多通关笔记在这一段写得含糊。我以自己的实际过程为准说清楚哪些依赖版本、哪些是通用思路。Level 14版本差异最大的一个关卡先看源码再动手。第十四关在不同版本的 XSS-labs 里实现差异很大有的版本是拼 iframe src有的版本涉及外链资源加载网上流传的 payload 经常因为浏览器版本不同而失效。我当时卡了十分钟最后直接翻开level14.php看源码才定位到它把某个参数原样拼进了标签属性里。这里我想分享一个所有靶场通用的方法遇到卡关直接看同目录里的 PHP 源码。靶场就是用来学的看源码不算作弊反而是最高效的学习路径。源码会直接告诉你三件事变量从哪来、过滤了什么、输出到了哪个上下文。有了这三个信息剩下来的就是选 payload 的问题。Level 15利用 AngularJS 的 ng-include 做模板包含。第十五关的源码引入了 AngularJS并且页面里有一个ng-include指令它的src属性值是由我们传入的参数控制的。ng-include会把src指向的地址当成模板片段加载渲染如果这个地址本身是一个包含 XSS 的页面脚本就会在渲染时执行。我当时的做法是让src指向第一关并带上 XSS 参数level1.php?namescriptalert(1)/script前面的单引号用来闭合src属性后面拼接的 URL 加载出来的内容里包含script于是弹窗。这一关如果完全不了解 AngularJS 的渲染机制根本无从下手它提醒我前端框架的能力有时会被开发者当成安全边界但把用户输入直接塞进模板指令里照样能变成 XSS。Level 16空格被过滤用控制字符代替。最后一关的过滤规则非常刁钻——它把输入里的空格直接替换掉了。没有空格很多 payload 都写不出来比如img src1 onerroralert(1)的 payload 中间全是空格。我当时试了 Tab 和换行最终用 URL 编码的换行符%0a成功svg%0aonloadalert(1)或者用图片写法img%0asrc1%0aonerroralert(1)%0a在 URL 解析后是换行符而部分浏览器在解析 HTML 属性时允许把换行符当作空白分隔符。服务端只过滤了物理空格字符却放过了其他空白字符。这关算是把这套靶场的终极绕法串了起来过滤永远只能基于某一种字符集合而 HTML 语法允许的空白字符远不止空格一个。4. 总结XSS-labs 真正教会我的三件事16 关刷完之后我没有急着继续刷下一个靶场而是回头把每一关的源码又翻了一遍。这个过程比刷题本身更值钱。如果要我用三句话总结我会说先看过滤再谈绕过、过滤只能处理字符串不能处理语义、要建立所有数据都是输入源的意识。4.1 第一件事先看过滤再谈绕过不要盲目换 payload很多新手卡关时有个习惯看到一个 payload 不灵就换另一个一个个试到弹窗为止。这样就算过了关你学到的东西也很有限。正确做法是先把页面源码、尤其是服务端 PHP 源码打开看清它到底对输入做了什么操作。比如第五关和第七关表面看都是过滤了 script但一个是替换关键字、一个是删除关键字对应的绕过方式完全不同。替换就想办法绕过关键字删除就想办法双写。信息越充分payload 越精准。我把这个流程固化成了三步观察回显位置输入是出现在 HTML 标签之间还是属性里面还是在 JS 代码里观察过滤规则开发者在输出前对输入做了什么是转义、替换还是删除根据过滤规则设计 payload属性位置就考虑引号闭合HTML 位置就考虑标签注入过滤敏感词就考虑大小写、编码、双写。4.2 第二件事过滤只能处理字符串而浏览器处理的是语义XSS-labs 最后几关反复在讲一件事服务端的过滤规则面对的是字符串但浏览器执行的是渲染后的语义。举个例子第 8 关的javas#99;ript:服务端看到的是javas#99;ript:这个原始字符串里面没有javascript:这个危险词但浏览器在解析href属性时先做 HTML 实体解码然后把解码后的javascript:alert(1)交给执行环境。于是过滤规则和执行语义之间出现了一条缝。这也是为什么总有人觉得 XSS 绕过没规律、纯靠背。其实规律是有的你只需要找到一个合法的编码层让浏览器解码后的语义命中你的 payload同时让服务端在进行字符串比较时看到的是一个无害的文本。4.3 第三件事请求头里的回显是反射型 XSS 最容易漏掉的地方第 11~13 关分别把 XSS 藏进了 Referer、User-Agent 和 Cookie。这三关做题时我的浏览器地址栏完全派不上用场只能靠 Burp Suite 改包。很多人在真实测试中只盯着 URL 参数和表单字段忽略了请求头但有些系统的页面恰恰会把这些请求头直接打印出来。我现在做 XSS 测试时养成了一套固定流程先抓包看一次完整请求把 GET/POST 参数、Referer、User-Agent、Cookie 分别替换成同一个探测字符比如 xss;回显页面里哪一个位置出现了这个字符就说明哪一处是注入点针对注入点的上下文定制 payload。这套流程前半部分就是 XSS-labs 第 10~13 关的翻版。刷过靶场之后再回到真实测试场景至少心里有数不会只会在地址栏塞script。5. 后续扩展装完 XSS-labs 之后我建议你再做这些事靶场刷完不是终点它其实是给后面更复杂的学习做铺垫。我个人在刷完 XSS-labs 之后又接着做了几件事都在同一个 Windows 环境里成本很低建议你直接照抄。5.1 在同一套环境里继续装 Pikachu 和 DVWAXSS-labs 专精 XSS但 XSS 经常会和其他漏洞配合使用比如存储型 XSS 配合 CSRF 盗取管理员信息、XSS 配合文件上传构造更隐蔽的攻击链。这时候就需要 Pikachu、DVWA 这种综合性靶场来补充。Pikachu 的部署方式和 XSS-labs 类似同样是解压到 WWW 目录初始化一下数据库就能访问。它的 XSS 模块里有反射型、存储型、DOM 型、XSS 绕过之 htmlspecialchars 这几个经典分类正好能把 XSS-labs 里没覆盖到的场景补上。DVWA 则建议把安全等级切到 medium 和 high 各刷一遍因为它的过滤模型更贴近真实生产环境的半吊子过滤。两个靶场都可以继续用 phpStudy 的环境不需要额外配置。5.2 用 Burp Suite 把第 11~13 关重新做一遍刷 XSS-labs 的时候很多人是第一次用 Burp Suite操作还不熟练改包要改半天。我建议过几天之后回头把 11、12、13 关用 Burp Suite 的 Repeater 功能完整重做一遍要求自己不看笔记自己构造 payload。Repeater 的好处是每次修改请求头都能立刻看到响应变化非常适合观察改了哪一处、回显位置怎么变。我之前在初刷时是看着教程改包重刷时才发现自己其实能独立改出来这种看起来会了和真会了之间的差距只有重做一遍才能填平。5.3 学习用脚本验证 XSS摆脱必须开浏览器靶场刷完后我开始尝试不用浏览器、只用 Python 脚本去发送 payload 并分析响应内容。原因很简单真实测试中你不可能总有一个带图形界面的浏览器环境而且很多场景需要批量验证。大致思路是用requests库发送一个包含 payload 的请求把响应 HTML 拿回来检查 payload 是否被原样输出、被过滤成什么样子。下面是一个最简单的最小脚本可以用来观察第 1 关的表现import requests payload scriptalert(1)/script url http://127.0.0.1/xss-labs/level1.php resp requests.get(url, params{name: payload}) print(resp.text)如果响应里包含完整的scriptalert(1)/script说明服务端没过滤浏览器加载后会执行。如果变成了lt;scriptgt;之类说明被转义了。用脚本测试的最大好处是能快速看到原始输入和回显输出之间的差异这个差异本身就是判定漏洞是否存在的关键。我个人的体会是XSS-labs 刷完一遍只是入了门真正让这套技能长在身上是在之后反复用源码分析、用 Burp 重测、用脚本验证这三件事里完成的。每次回头重看都会发现当初某个关卡其实还有更简单的解法或者当初根本没弄懂为什么这个 payload 能绕过去。靶场的价值从来不在于刷完而在于刷的过程够不够慢、够不够细。
阅读完成 · 觉得有帮助?