1. 项目概述为什么我们需要一个XSS靶场如果你是一名Web安全爱好者或者正在学习渗透测试那么“XSS-Labs”这个名字你一定不陌生。它不是一个商业产品而是一个由安全社区爱好者自发维护的、专门用于练习跨站脚本攻击与防御的在线靶场。我第一次接触它是因为在真实渗透测试中遇到了一个非常刁钻的过滤规则常规的payload全部失效急需一个能系统化训练绕过思维的环境。市面上的综合靶场如DVWA、Pikachu虽然也有XSS模块但关卡设计往往比较基础或分散而XSS-Labs则像一本精心编排的“XSS绕过技巧习题集”从最基础的反射型XSS开始层层加码逐步引入各种过滤和防护机制逼迫你去思考如何变形、如何组合、如何利用上下文环境。这个靶场的核心价值在于“实战模拟”。它模拟了真实开发中可能出现的各种不安全的代码写法比如直接在innerHTML中输出用户输入、使用了不安全的JavaScript函数如eval()或document.write()、尝试用黑名单过滤但存在遗漏等等。通过通关这一个个关卡你不仅能记住一堆payload更重要的是能建立起一套完整的绕过分析思路看到过滤规则能立刻想到几种可能的绕过方向看到输出点能快速判断可利用的上下文和闭合方式。这对于应对真实世界的WAF、各种框架的默认过滤机制以及开发人员自定义的防护逻辑至关重要。接下来我将结合我通关XSS-Labs前十关的实战经验为你拆解每一关的核心考点、绕过思路以及那些容易被忽略的细节技巧。2. 环境准备与靶场搭建2.1 靶场获取与部署XSS-Labs通常以PHP源码的形式在GitHub等平台传播。部署它非常简单你只需要一个能运行PHP的Web服务器环境。对于初学者我强烈推荐使用XAMPP或PHPStudy这类集成环境它们能一键安装Apache、MySQL、PHP省去大量配置麻烦。下载源码从可靠的源如GitHub上的开源项目获取XSS-Labs的完整源码包。确保下载的版本是完整的包含所有关卡文件通常命名为level1.phplevel2.php等和一个统一的入口文件index.php。部署到Web目录将解压后的整个XSS-Labs文件夹复制到你的Web服务器根目录下。例如对于XAMPP就是htdocs文件夹对于PHPStudy就是WWW文件夹。访问靶场在浏览器中输入http://localhost/xss-labs/假设文件夹名为xss-labs即可看到关卡选择界面。通常界面非常简洁就是一个数字列表点击即可进入对应关卡。注意请务必在本地或授权的测试环境中搭建和练习。未经授权对任何线上系统进行测试都是非法且不道德的。2.2 必备工具与浏览器设置工欲善其事必先利其器。除了靶场本身准备好以下工具能让你的测试事半功倍浏览器与开发者工具Chrome或Firefox是首选它们的开发者工具F12打开是核心分析武器。重点关注“元素”Elements/Inspector标签页查看HTML结构以及“控制台”Console标签页查看JavaScript错误和执行结果。代理抓包工具Burp Suite社区版或OWASP ZAP。这类工具能拦截、查看和修改浏览器发送的HTTP请求是手动测试和绕过过滤的关键。你可以通过它们修改URL参数、POST数据、Cookie等尝试各种payload变体而无需反复在浏览器地址栏输入。浏览器扩展一些扩展能辅助测试如用于快速编码/解码的Hack-Tools或者用于修改页面内容的EditThisCookie。但核心依赖仍是开发者工具和代理工具。编码工具意识不需要单独软件但要熟知URL编码、HTML实体编码、JavaScript Unicode编码等概念。浏览器的地址栏、控制台都可以直接进行简单的编码解码测试。一个关键的浏览器设置为了更清晰地看到payload的执行结果建议在测试时暂时关闭Chrome的XSS Auditor如果版本较老或相关内置过滤器。新版本Chrome中确保不要开启过于严格的内容安全策略CSP测试模式以免本地靶场的弹窗被浏览器本身拦截干扰你对漏洞是否存在的判断。不过XSS-Labs的设计通常能兼容现代浏览器的基本安全机制。3. 前十关核心技巧与绕过思路逐关精析下面我将以最常见的XSS-Labs关卡设计为例逐一解析前十关的典型场景和突破方法。请注意不同版本的靶场具体代码可能略有差异但核心思路是相通的。3.1 第一关无过滤的反射型XSS场景一个简单的搜索框输入内容后直接回显在页面上。源码关键点猜测?php echo $_GET[‘keyword’]; ?或类似代码直接将GET参数输出到HTML中。绕过思路无需绕过构造基本payload即可。Payload示例scriptalert(‘xss’)/script实操与解析 在搜索框输入上述payload提交。查看页面源代码你会发现你的输入被原封不动地插入到了某个HTML标签之间例如在一个h2标签内。这构成了一个最经典的反射型XSS。成功弹窗。技巧心得第一件事永远是看源码提交payload后立即右键查看页面源代码或使用F12开发者工具确认你的输入被放置在页面的哪个位置是否被HTML标签包裹。这决定了你后续的payload构造方式。使用非侵入性验证在真实不确定的环境可以先使用img src1 onerrorconsole.log(1)这类payload。alert会干扰测试而console.log只在控制台输出更为隐蔽友好。3.2 第二关搜索框内的属性值转义场景输入内容被放入一个HTML标签的属性值里比如input value”你的输入”。源码关键点猜测input type”text” value”?php echo $_GET[‘keyword’]; ?”。绕过思路需要先闭合双引号然后引入事件处理器或新的标签。Payload示例”scriptalert(‘xss’)/script或” onmouseover”alert(‘xss’)实操与解析 输入”scriptalert(‘xss’)/script。首先开头的双引号”会闭合value属性的前一个双引号。紧接着的会闭合input标签。然后我们成功在页面中插入了新的script标签。查看源码会看到类似input type”text” value””scriptalert(‘xss’)/script”的结构。技巧心得属性值上下文当输出点在属性值内时script标签可能无法直接执行因为浏览器不会解析属性值内的标签。优先考虑使用事件处理器属性如onclickonmouseoveronerror等。闭合符号选择观察页面源码确定属性是用单引号还是双引号包裹使用对应的符号进行闭合。有时甚至没有引号此时直接用空格和事件处理器即可。3.3 第三关单引号属性与初步过滤场景输出点位于单引号包裹的属性值内并且服务端可能对双引号或、做了简单处理。源码关键点猜测input value’?php echo htmlspecialchars($_GET[‘keyword’] ENT_QUOTES); ?’ 但可能只过滤了双引号。绕过思路尝试闭合单引号并使用事件处理器。因为和可能被转义插入新标签困难。Payload示例’ onmouseover’alert(/xss/)实操与解析 输入’ onmouseover’alert(/xss/)。开头的单引号闭合了value属性然后添加了onmouseover事件处理器。注意这里事件处理器的值我用/xss/代替了字符串这是一种简写方式。最终构造出input value” onmouseover’alert(/xss/)’。当鼠标滑过输入框时触发弹窗。技巧心得利用JavaScript字面量在事件处理器中alert(1)、alert(‘1’)、alert(/1/)通常是等价的。当引号被过滤时使用正则表达式字面量/.../是一个常用的绕过技巧。大小写混淆如果靶场对onmouseover这类关键词进行黑名单匹配尝试OnMoUsEoVeR或onmouseover末尾加空格有时能绕过简单的基于字符串匹配的过滤。3.4 第四关双引号属性与标签过滤场景输出点回到双引号属性但服务端可能过滤或删除了和。源码关键点猜测使用str_replace函数将和替换为空。绕过思路既然无法插入新标签script那就专注于利用现有标签的属性。闭合双引号添加事件处理器。Payload示例” onmouseover”alert(‘xss’)实操与解析 这个payload和第三关类似只是引号不同。输入后构造出input value”” onmouseover”alert(‘xss’)”。这里的关键是理解过滤了只是阻止了你创建新标签但并没有阻止你在现有标签上添加新的属性。技巧心得黑名单过滤的局限性只过滤是极其不安全的。攻击者完全可以在不引入新标签的情况下完成攻击。这提醒我们安全的输出必须结合上下文进行编码或白名单过滤。多种事件测试除了onmouseover还可以测试onfocus、onblur、onload针对img等标签等。不同场景下可触发的事件不同。3.5 第五关对script和on关键词的过滤场景服务端开始过滤script、on等明显的关键词。源码关键点猜测$keyword str_replace(‘script’ ‘’ $_GET[‘keyword’]);以及$keyword str_replace(‘on’ ‘’ $keyword); 可能不区分大小写。绕过思路使用非script标签和非on事件。HTML5提供了很多可以执行JavaScript的标签和属性。Payload示例”a href”javascript:alert(‘xss’)”click/a或”svg/onloadalert(‘xss’)实操与解析使用a标签的href属性javascript:伪协议可以执行代码。但注意这需要用户点击链接。不过在证明漏洞存在时是有效的。使用svg标签SVG标签内嵌在HTML中其onload事件可以在标签加载时自动触发且onload中的on可能因为字符串替换变成load但svg onload作为一个整体如果过滤是简单的str_replace(‘on’ ”), 会得到svg load 这无法执行。所以需要看过滤的具体实现。更可靠的可能是使用img src1 onerroralert(1)但onerror里的on也会被过滤。这时可以尝试双写绕过img src1 oonnerroralert(1)如果过滤函数只执行一次替换会得到onerror。技巧心得双写绕过这是对抗简单字符串替换的经典方法。如果过滤script 就写scrscriptipt 过滤后变为script。探索冷门标签和属性除了script和on事件还有iframe、embed、object的src属性支持javascript:、details的ontoggle事件、body的onpageshow事件等。积累这些向量很重要。3.6 第六关大小写绕过与黑名单扩展场景黑名单关键词增加但过滤可能对大小写敏感。源码关键点猜测黑名单包含scriptonsrcdata等但使用如str_ireplace不区分大小写或str_replace区分大小写。绕过思路如果过滤区分大小写尝试大小写混合。如果黑名单有遗漏尝试未过滤的标签或事件。Payload示例”ScRiPtalert(‘xss’)/sCrIpT或”img sRc1 onErRoralert(1)实操与解析 输入”ScRiPtalert(‘xss’)/sCrIpT。如果后端使用str_replace(‘script’ ”) 它不会匹配ScRiPt因此payload得以保留并执行。查看源码确认标签被正确插入。技巧心得测试过滤逻辑通过输入script、SCRIPT、ScRiPt观察页面源码中它们的变化可以推断后端是直接删除、转义还是替换以及是否区分大小写。这是信息收集的关键一步。使用编码试探有时可以尝试HTML实体编码的一部分如script看后端是否会解码。但通常简单的黑名单过滤不会做解码处理。3.7 第七关关键字删除与双写绕过场景黑名单继续扩大并且对关键词执行删除操作。源码关键点猜测$keyword str_replace(array(‘script’ ‘on’ ‘src’ ‘data’ ‘href’) ” $_GET[‘keyword’]);。绕过思路双写绕过。因为删除是直接替换为空所以双写后删除中间的关键词两边的字符会拼接成新的关键词。Payload示例”scrscriptiptalert(‘xss’)/scrscriptipt或” oonnmouseover”alert(‘xss’)实操与解析 以scrscriptipt为例。后端查找script并删除那么从scrscriptipt中删除中间的script后剩下的部分拼接起来就是script。最终页面中呈现的就是script标签。技巧心得精确双写确保你双写的部分正好是过滤词。如果过滤on和script那么oonn和scrscriptipt都要用上。组合测试一个payload里可能同时需要绕过多个关键词过滤例如img srsrcc1 oonnerroralert(1)。3.8 第八关HTML实体编码与javascript:协议过滤场景输入内容被输出到a标签的href属性中并且服务端可能对javascript:进行了过滤。源码关键点猜测a href”?php echo htmlspecialchars($_GET[‘link’]); ?”Click/a 并且可能额外检查href值是否以javascript:开头。绕过思路利用HTML实体编码绕过htmlspecialcharshtmlspecialchars默认只转义”’。对于已经位于属性值内的内容如果属性值本身用双引号包裹那么双引号被转义成quot; 我们无法闭合。但我们可以尝试注入和#来构造HTML实体浏览器在解析href属性时会对其进行解码。绕过javascript:过滤使用大小写、插入空白符、使用java script:Tab、java\nscript:等。Payload示例javascript:alert(‘xss’)- 尝试JavaSCript:alert(‘xss’)或javascrip#x74;:alert(1)。实操与解析 首先输入简单的javascript:alert(1)查看源码发现被转义或过滤。尝试JavaSCript:alert(1)。如果不行尝试利用HTML实体javascrip#x74;:alert(1)。这里#x74;是字符t的十六进制HTML实体。当浏览器解析href属性时会将其解码为t从而组合成javascript:。技巧心得属性值上下文编码在HTML属性值中浏览器会进行HTML实体解码。这为我们绕过对特定字符串的过滤提供了可能。可以构造#xXX;十六进制或#XXX;十进制形式的实体来替换关键词中的字符。协议伪指令javascript:协议非常强大但也容易被WAF重点关照。除了它还可以在允许的情况下测试data:协议它可以承载HTML或JavaScript代码但通常也被严格过滤。3.9 第九关链接合法性校验与绕过场景在第八关基础上服务端增加了校验要求href的值必须包含http://否则不显示链接或做其他处理。源码关键点猜测检查$_GET[‘link’]中是否包含子串http://。绕过思路在javascript:协议伪指令前或后拼接一个合法的http://链接利用javascript:协议执行代码的特性或者尝试注释掉后面的部分。Payload示例javascript:alert(‘xss’);//http://example.com或http://example.com#javascript:alert(‘xss’)实操与解析方法一注释法javascript:alert(‘xss’);//http://example.com。//在JavaScript中是单行注释后面的http://example.com会被注释掉不会影响前面代码的执行。同时整个字符串包含了http:// 可能通过校验。方法二锚点法http://example.com#javascript:alert(‘xss’)。#后面是URL的片段标识符hash通常不会发送到服务器。如果校验只是简单的字符串包含http://那么这个payload可以通过。当用户点击时浏览器会跳转到http://example.com但片段标识符中的javascript:协议可能在某些浏览器的特定上下文中不被执行成功率较低。注释法更可靠。技巧心得理解校验的松紧校验是“必须包含”还是“必须以…开头”如果是“必须包含”注释法很好用。如果是“必须以…开头”可能需要像http://javascript:...这样的payload但这通常无效因为http://后面应该跟主机名。利用JavaScript语法灵活性分号、注释//或/* */、字符串拼接等都可以用来构造既能通过校验又能执行的payload。3.10 第十关隐藏参数与事件触发场景页面中存在隐藏的表单字段input type”hidden”其值由URL参数控制并且这个值最终会以某种方式影响页面可能被输出到JavaScript变量或某个属性中。源码关键点猜测URL中存在一个参数如?keywordtest 这个值被赋给一个隐藏输入框的valueinput type”hidden” name”hiddendata” value”?php echo $_GET[‘keyword’]; ?”。然后页面的某个JavaScript会读取这个value并进行处理。绕过思路寻找输出点首先通过查看源码找到隐藏输入框。确认其name和id。追踪数据流在页面的JavaScript代码中搜索这个name或id看它的值被用在了哪里。可能被直接eval() 或者拼接进innerHTML 或者作为document.write的参数。构造闭合根据数据被使用的上下文JavaScript字符串、HTML字符串等构造合适的payload进行闭合和注入。Payload示例假设JS代码是var data document.getElementById(‘hiddendata’).value; document.write(data);那么我们可以注入/scriptscriptalert(‘xss’)/script。但这样会破坏原有JS结构。更常见的是如果数据被用在innerHTML或类似场景可以直接注入HTML/JS标签。 如果JS代码是eval(‘var x “‘ document.getElementById(‘hiddendata’).value ‘”;’);那么我们需要闭合字符串并注入代码”; alert(‘xss’);//。这样拼接后变成eval(‘var x “”; alert(‘xss’);//”;’); 成功执行alert。实操与解析 这一关没有固定的payload完全取决于后端代码如何“隐藏”地使用这个参数。你需要像一个侦探一样提交一个简单值如test123。F12打开开发者工具在“元素”面板找到隐藏的input确认其值已被设置。在“源代码”Sources面板或“元素”面板的script标签中全局搜索test123这个字符串找到它是如何被JavaScript代码引用的。分析引用处的代码上下文是字符串拼接、直接输出还是函数参数根据上下文构造payload并在浏览器控制台先模拟测试拼接后的代码是否语法正确、能否执行。技巧心得学会代码审计这一关开始涉及简单的客户端代码审计能力。这是真实世界XSS挖掘中至关重要的一环很多XSS漏洞源于不安全的JavaScript动态操作DOM。控制台是你的沙盒在不确定payload是否有效时先在浏览器控制台模拟执行拼接后的完整JavaScript代码确认无语法错误且能触发预期效果再提交到服务器。注意字符转义如果数据被放在JavaScript字符串内被单/双引号包裹你需要闭合引号并确保注入的代码不会因为包含特殊字符如未转义的单引号而破坏语法。使用\进行转义或者用String.fromCharCode等方式构造字符串。4. 通用绕过思路与高级技巧汇编通过前十关我们已经见识了从简单到复杂的各种过滤。下面我系统性地总结一下XSS绕过的核心思路和高级技巧这些在后续关卡和真实环境中都极其有用。4.1 基于过滤逻辑的绕过这是最直接的对抗方式针对后端采取的防护措施进行突破。字符串匹配与删除大小写绕过ScRiPtOnClIcK。双写绕过scrscriptiptoonn。插入干扰字符在关键词中插入浏览器可忽略而过滤器可能不可忽略的字符。例如在HTML标签名或属性名中插入/、\0空字符、换行符、Tab符等。img/src1/onerroralert(1)利用/代替空格。svg onloadalert(1)在onload前插入一个空字符需URL编码为%0c。嵌套标签某些过滤器可能只检查一层标签。scrscriiptptalert(1)/scr/scriiptpt 如果过滤器递归删除不彻底可能留下script。正则表达式缺陷不匹配多行如果正则表达式没有使用/m标志或.不匹配换行符可以利用换行符分割payload。贪婪/非贪婪匹配了解正则的匹配模式可能构造字符串使其匹配失败或匹配到非预期部分。回溯绕过构造超长或特定模式的字符串可能使正则引擎陷入灾难性回溯导致过滤失效在WAF场景更常见。4.2 基于HTML/JS解析差异的绕过浏览器解析HTML和JavaScript的方式有时与后端过滤器的理解不同利用这些差异是高级绕过的关键。HTML实体编码如前所述在HTML文本或属性值中开头的实体会被浏览器解码。可以构造img src1 #111;nerroralert(1)#111;是o。如果过滤器在解码前检查就可能被绕过。JavaScript编码Unicode转义在JavaScript字符串中\u0061表示字符a。可以构造\u0061lert(1)。十六进制/八进制转义\x61lert(1)十六进制\141lert(1)八进制。利用eval()和String.fromCharCode如果输出点在一个eval的参数里可以注入String.fromCharCode(97 108 101 114 116 40 49 41)来动态生成alert(1)的代码。标签属性与语法宽松性属性值可不加引号img src1 onerroralert(1)是有效的。如果过滤器只检查引号内的内容这可能绕过。属性分隔可用多种字符除了空格Tab、换行、/在标签名后等都可以分隔属性。自闭和标签img src1 onerroralert(1) / 末尾的/是可选的但有时能绕过对的过滤检查。利用SVG/MathML等XML命名空间SVG标签内的某些事件处理器或脚本执行方式可能与HTML略有不同有时能绕过针对HTML的过滤规则。例如svgscriptalert(1)/script/svg或svg onloadalert(1)。4.3 基于上下文切换的绕过有时直接注入失败但可以尝试将执行上下文切换到另一个更有利的解析器。从属性切换到HTML内容通过闭合当前属性甚至当前标签将注入点“提升”到HTML正文层面然后插入新的标签。这是第二关到第五关的主要思路。从JavaScript字符串切换到代码通过闭合字符串引号并加上分号或注释结束当前语句开始新的JavaScript语句。这是第十关和很多DOM型XSS的利用方式。例如’; alert(1);//或/scriptscriptalert(1)/script。利用textarea或title等标签的解析规则这些标签内的内容通常被当作纯文本直到遇到闭合标签。如果页面中已存在这些标签且未闭合注入闭合标签可能逃逸出来。4.4 利用浏览器特性与怪异模式浏览器为了兼容旧网页存在一些特殊的解析行为。字符集问题如果页面指定了错误的字符集或未指定某些特殊字节序列可能被浏览器以不同方式解析导致过滤器识别失败。script标签的charset属性理论上可以指定非标准字符集但实用价值不高。过时或不推荐的技术如img dynsrclowsrc1 onerroralert(1)利用旧属性了解即可现代浏览器支持度差。5. 防御视角从攻击中学习如何编写安全代码作为一名开发者从这些绕过技巧中我们应该汲取教训知道如何避免写出存在漏洞的代码。原则绝不信任用户输入。这是黄金法则。所有来自客户端的数据URL参数、POST表单、Cookie、HTTP头都必须视为不可信的。根据输出上下文进行编码输出到HTML正文使用HTML实体编码。例如PHP的htmlspecialchars($str ENT_QUOTES | ENT_HTML5 ‘UTF-8’)。ENT_QUOTES会编码单双引号非常重要。输出到HTML属性值同样使用HTML实体编码并确保属性值总是用引号单或双包裹。输出到JavaScript代码或变量使用JavaScript编码。不要简单用addslashes 应使用专门的函数如json_encode()用于生成JSON或确保数据被放在引号内并进行正确的转义。更好的做法是避免将用户输入直接拼接到script标签内而是通过>