
简介这是一份面向网络安全初学者与Web安全入门者的XSS专项练习靶场基于xss-labs-master搭建通过一系列循序渐进的挑战关卡帮助学习者理解反射型、存储型与DOM型XSS的触发原理并掌握注入点识别、payload构造与常见防御思路。压缩包共49个文件以24个php关卡脚本为核心辅以15个png与1个jpg关卡示意图、3个html入口页面、2个js脚本及4个swf测试文件整体约1.08MB体积轻量便于本地部署。目前已有556人学习下载适合作为课堂实验或自学练手素材。资源完整保留了各关卡源码与配套图示读者可对照关卡逐一复现攻击场景在解题中熟悉开发者工具调试、输入过滤绕过与CSP、HTTP-only等防护要点形成从原理到实战的闭环认知。1. 入门靶场值不值得刷先把预期摆正很多人第一次搜 xss 靶机脑子里想的是“找个环境把 payload 打一遍就完事”。我当初也这么想结果第一轮刷下来除了记住几个弹窗标签遇到真实输入点还是不知道从哪下手。后来复盘才明白入门级靶场的价值不在“通关”而在于它把 XSS 的几种典型触发位置——反射、存储、DOM、属性上下文、过滤绕过——压缩成一条低成本的练习线让你在几乎零搭建负担的前提下把“输入点在哪、输出点在哪、上下文是什么”这套判断肌肉练出来。这份资源就是一套入门级别的 xss-labs 靶场需要自行下载搭建搭建过程网上资料很多不展开。它适合三类人刚接触 Web 安全、想建立第一手手感的新手想拿它给团队做内部演练底座的熟手以及刷过一遍但当时没做笔记、想回头补上下文判断的人。下面我按“怎么刷才有用”来讲不讲通关攻略讲判断依据和踩过的坑。2. 靶场环境怎么起搭建与第一轮摸底2.1 搭建前先想清楚的三件事入门靶场最容易翻车的地方不是漏洞本身是环境。我一般会先确认三件事再动手运行环境是独立隔离的别和日常开发环境混在一起靶场目录的读写权限明确因为部分关卡涉及文件写入或存储型输入浏览器准备两个一个日常用、一个专门刷靶场避免缓存和插件干扰 payload 的实际表现。搭建本身按网上通用流程走即可PHP 运行环境加 Web 服务把靶场目录挂到站点根目录下访问入口页能看到关卡列表就算起好了。这里不写具体命令因为不同系统差异大照搬反而容易卡住。真正要盯的是访问入口页时有没有报错、关卡链接能不能正常跳转、提交参数后页面是否回显——这三步通了环境就算可用。提示靶场只在本机或隔离网络里跑不要暴露到公网。入门靶场本身设计上就带漏洞暴露出去等于给自己挖坑。2.2 第一轮摸底先分类再动手环境起来后别急着逐关贴 payload。我习惯先花二十分钟做一轮摸底把每一关的输入点位置、输出点位置、是否有过滤、输出落在 HTML 的哪个位置用一张表记下来。这张表后面会反复用。观察项怎么看记录什么输入点URL 参数、表单、搜索框参数名、提交方式输出点页面源码里搜自己输入的内容出现在标签内、属性内还是脚本内过滤迹象输入特殊字符看是否被转义或删除哪些字符被处理、处理方式上下文输出位置前后各是什么标签决定 payload 的闭合方式这张表的意义在于XSS 的本质是“输入被当成代码执行”而能不能执行取决于输出落在什么上下文。同样是script标签落在 HTML 正文里能跑落在属性值里可能要先闭合引号落在textarea里要先闭合标签。入门靶场把这些上下文拆成一关一关你如果只记 payload 不记上下文换个页面就废。2.3 用浏览器开发者工具做输出点定位摸底阶段最实用的工具就是开发者工具的源码视图。提交一个带标记的字符串比如test123然后在源码里搜这个字符串看它出现在第几行、前后是什么。这一步比背 payload 重要得多。// 在控制台快速定位输入内容出现在 DOM 的哪里 // 把 test123 换成你实际提交的标记字符串 const marker test123; const walker document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); let node; while ((node walker.nextNode())) { if (node.nodeValue.includes(marker)) { console.log(文本节点命中:, node.parentElement); } } // 属性里的输出不会出现在文本节点需要额外遍历属性 document.querySelectorAll(*).forEach(el { for (const attr of el.attributes) { if (attr.value.includes(marker)) { console.log(属性命中:, el, attr.name); } } });这段脚本做两件事遍历文本节点找标记字符串再遍历所有元素的属性找标记字符串。前者对应正文上下文后者对应属性上下文。跑一遍就能知道这一关的输出落在哪类位置比肉眼翻源码快。参数只有一个标记字符串换成你提交的内容即可。注意它只做定位不判断可利用性可利用性还要结合前后标签看。3. 反射、存储、DOM 三类怎么区分着打3.1 反射型看回显位置决定闭合方式反射型的特点是提交即回显、不落库。入门靶场里大部分关卡属于这一类。打这类关卡的核心不是 payload 本身是回显位置的上下文。我一般按四类位置处理第一类输出在 HTML 正文里前后没有特殊标签包裹直接构造脚本标签即可。第二类输出在标签属性值里比如value你的输入这时要先闭合引号再引入事件或标签。第三类输出在script块内部要考虑引号闭合和语句拼接。第四类输出在注释或textarea里要先闭合包裹结构。!-- 属性上下文先闭合引号再引入事件处理器 -- onmouseoveralert(1) !-- 正文上下文直接引入标签 -- scriptalert(1)/script !-- script 块内闭合字符串再执行 -- ;alert(1);//这三段分别对应属性、正文、脚本块三种上下文。实际用的时候要看回显前后到底是什么不能照抄。比如属性上下文里如果引号被转义了闭合这条路就走不通得换思路。这也是入门靶场最值得练的地方同一类上下文过滤不同打法就不同。3.2 存储型先确认落库再谈触发存储型和反射型的区别在于输入被持久化每次访问页面都会触发。入门靶场里这类关卡通常出现在留言、评论、昵称一类功能上。判断方法很直接提交后刷新页面看内容还在不在换个页面或换个入口访问看内容会不会再次出现。打存储型要注意两点。一是确认落库位置有些关卡提交后只在当前页面显示刷新就没了那还是反射型。二是确认输出上下文存储型内容往往在列表页和详情页各输出一次两处上下文可能不同要分别看。我踩过的坑是只看了提交后的回显页没看列表页结果列表页里输出在属性里payload 根本没触发。3.3 DOM 型盯住前端取值链路DOM 型的特殊之处在于输入不经过服务端回显而是被前端脚本读取后写入页面。判断方法是看源码里有没有从 URL、location、document.referrer一类来源取值再通过innerHTML、document.write一类方式写入。// 典型的 DOM 型取值链路入门靶场里常见这种模式 // 从 URL 片段或参数取值 const input location.hash.slice(1) || new URLSearchParams(location.search).get(q); // 危险写入把用户可控内容当 HTML 解析 document.getElementById(output).innerHTML input;这段代码展示的是取值到写入的链路。打这类关卡payload 要放在前端真正读取的那个来源里放错位置服务端根本收不到自然没反应。常见误区是拿反射型的思路去打 DOM 型参数提交了但前端读的是另一个来源白忙一场。判断依据就是源码里那行取值语句盯住它读的是什么payload 就放什么。4. 过滤与编码绕过的排查思路4.1 先判断过滤发生在哪一层绕过的前提是知道过滤在哪。入门靶场里过滤可能发生在服务端也可能发生在前端还可能两边都有。判断方法提交一个带特殊字符的输入看回显里字符是被删除、被转义还是原样保留。被删除说明有黑名单过滤被转义说明有编码处理原样保留说明这一层没管。我一般会提交一组探测字符比如 ( ) /逐个看回显。这一步能快速画出过滤轮廓哪些字符被处理、处理成什么样。有了轮廓再想绕过比盲目试 payload 高效得多。4.2 常见绕过方向与适用条件绕过方向适用条件注意点大小写混写黑名单只匹配小写标签名部分解析器对大小写不敏感标签属性事件标签被过滤但属性未过滤需要能闭合到属性上下文编码变形过滤在解码前发生确认解码发生在哪一层双写拼接过滤只做一次替换替换后拼接可能还原关键字这张表不是 payload 清单是判断方向。每个方向都有适用条件条件不满足硬套就是浪费时间。比如大小写混写在严格匹配的过滤器面前无效编码变形在过滤发生在解码之后时也无效。入门靶场的好处是每关过滤相对单一正好用来练“先判断再选方向”的习惯。4.3 绕过失败时看什么绕过失败别急着换 payload先看三样东西回显里你的输入变成了什么、浏览器控制台有没有报错、页面源码里最终落地的结构是什么。回显变形说明过滤规则控制台报错说明语法没闭合对源码结构说明上下文判断有没有错。这三样看完大部分失败原因就清楚了。我见过最常见的失败是上下文判断错以为在正文里其实在属性里payload 语法没错但位置不对。还有一种是把编码当绕过结果目标层根本不解码。回过头看绕过这件事七分靠判断、三分靠试判断对了试一两次就通判断错了试一百次也是碰运气。5. 刷靶场最容易踩的五个坑5.1 只记 payload 不记上下文现象同一段 payload 在某一关能弹窗换一关完全没反应。原因payload 是为特定上下文构造的换上下文后闭合关系不成立。解决每关先定位输出点上下文再决定 payload 结构把上下文和 payload 一起记进笔记。5.2 浏览器缓存和插件干扰判断现象改了 payload 但页面表现和上次一样或者弹窗被插件拦了。原因缓存了旧响应或安全插件拦截了脚本执行。解决刷靶场用独立浏览器配置关掉干扰插件必要时强制刷新或换无痕窗口。5.3 把存储型当反射型打现象提交后当前页有反应刷新就没了以为关卡坏了。原因没确认是否落库实际是反射型。解决提交后刷新、换入口访问确认内容是否持久化再决定打法。5.4 DOM 型 payload 放错来源现象参数提交了但页面毫无变化。原因前端脚本读的是另一个来源比如读 hash 而你提交的是 query。解决看源码里取值语句读的是什么payload 放到对应来源。5.5 环境暴露或权限过宽现象靶场跑起来后本机其他服务受影响或文件被意外改动。原因靶场和日常环境混用目录权限没隔离。解决独立目录、独立运行环境刷完即停不长期挂着。6. 从刷通关到会判断一条可复用的练习线刷完一轮入门靶场真正的收获不是记住了多少 payload而是形成了一条固定的判断线。我现在拿到任何一个输入点会按这个顺序走先找输入点在哪、提交方式是什么再提交标记字符串定位输出点然后看输出落在什么上下文接着探测过滤轮廓最后才构造 payload。这条线在靶场里练熟换到真实场景也能用因为真实场景无非是上下文更复杂、过滤更多层。给想认真练的人一个建议每关都写三行笔记——上下文是什么、过滤处理了什么、payload 为什么这么构造。三行不多但一轮下来就是一份自己的判断手册。我当初偷懒只记 payload第二轮刷的时候发现全忘了为什么这么写只能重来这就是没有后悔药的地方。# 刷靶场时的记录模板按关卡逐条填别嫌麻烦 # 关卡编号 # 输入点参数名 / 提交方式 # 输出上下文正文 / 属性 / 脚本块 / 注释 # 过滤表现哪些字符被处理、处理方式 # payload 结构闭合方式 执行方式 # 失败记录试过什么、为什么没成这个模板的作用是把“手感”变成可回看的东西。参数就那几个字段填起来一分钟但第二轮、第三轮复习时价值很大。尤其是失败记录那一栏很多人不记结果同一个坑反复踩。最后说一个具体技巧刷完每一关把 payload 里的执行部分换成无害的标记输出比如把弹窗换成往页面写一个固定字符串再跑一遍。这样做的好处是确认触发链路通了同时避免养成“看到弹窗就算成功”的惯性——真实场景里你要的是证明可利用不是弹窗本身。从那以后我每次刷靶场都强制走一遍“定位上下文、探测过滤、构造、验证链路”这四步少一步都不算过关。希望帮到你。本文还有配套的精品资源点击获取