恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Xss-Labs靶场1-10关实战:从反射型XSS到过滤绕过的完整思路
首页
资讯中心
/
Xss-Labs靶场1-10关实战:从反射型XSS到过滤绕过的完整思路
Xss-Labs靶场1-10关实战:从反射型XSS到过滤绕过的完整思路
发布时间:2026/9/11 2:46:58
刚刷完Xss-Labs前10关的时候我最大的感受是原来XSS并没有想象中那么玄乎但也绝对没有某些速成教程说的那么简单。它更像是一场“猜谜游戏”——服务端到底过滤了什么、输出位置在哪、用什么办法能把我们的代码送到浏览器解析器嘴里每一个环节都在考验对HTML解析机制的理解。这篇就基于我自己刷Xss-Labs靶场1到10关的完整过程把每一关的源码逻辑、绕过思路、最终payload和踩坑经验都拆开讲一遍。如果你正在学Web安全尤其是想系统入门XSS攻击这套靶场是非常好的练手材料。前10关难度是循序渐进设计的从“完全不过滤”到“各种花式过滤”刷完基本能建立一套自己的绕过思维框架。提示本教程仅供安全学习与合法的授权测试使用未授权环境下请勿直接应用文中payload。1. 准备工作与实验环境1.1 靶场部署方案Xss-Labs是一套基于PHP编写的本地靶场每一关对应一个独立的PHP文件核心逻辑都藏在源码的过滤函数里。部署方式很简单最常见的就是用phpstudy或者小皮面板跑一个Apache/Nginx环境把靶场文件夹丢进站点根目录就能开刷。我这里用的是phpstudy 8.1版本PHP版本选的7.4靶场文件放在C:\phpstudy_pro\WWW\xsslabs目录下浏览器访问http://127.0.0.1/xsslabs/就能看到关卡列表。如果你用的是其他集成环境注意一下站点根目录和PHP版本兼容性就行。部署完成之后还有一个建议把PHP错误提示打开方便调试。不同环境下源码细节可能有细微差别出问题时直接查看.php文件的原始代码比对着网上的教程猜要靠谱得多。1.2 浏览器与工具准备刷Xss-Labs最顺手的浏览器是Firefox或Chrome推荐装一个HackBar插件方便快速构造和发送payload。为什么不用Burp Suite全程代替因为前10关很多payload需要不断修改微调HackBar在浏览器里直接改URL参数反馈速度比来回开代理抓包快得多。另外一个非常重要的工具是浏览器的“检查元素”功能。每一关的payload发送之后右键查看页面源代码能直接看到服务端把我们的输入原样输出到了什么位置这是一个非常关键的判断依据——输出在标签内、属性内还是URL内直接决定了用什么类型的payload。我还习惯在浏览器里装一个SwitchyOmega虽然前10关用不上代理但这个习惯在后面刷更复杂的靶场时会省不少事。工具够用就行没必要一开始就弄得花里胡哨。2. 第1关到第5关反射型XSS的入门阶梯2.1 第1关毫无过滤的Hello World第1关的核心源码逻辑大概是这样的$str $_GET[name]; echo h2 aligncenter欢迎用户.$str./h2;代码非常简单$_GET[name]拿到的参数直接被拼进HTML的h2标签中间没有任何过滤或转义。这一关就是XSS的“Hello World”目的是让新手理解XSS最基本的发生条件用户输入未经过任何处理直接进入页面输出。直接构造URLhttp://127.0.0.1/xsslabs/level1.php?namescriptalert(1)/script如果浏览器拦截了刷新一下或者换Firefox再试payload就能弹窗。这里的alert(1)只是一个验证手段证明我们的脚本被浏览器执行了在实际攻击场景中这里可能会换成窃取Cookie、伪造登录表单、劫持会话的恶意脚本。第1关给我们的最大启示不是“哦原来可以弹窗”而是建立反射型XSS的最小模型用户输入 → 服务端取值 → 拼接到响应 → 浏览器解析执行。任何一步有防御攻击就不能成立。2.2 第2关双引号属性闭合与事件注入第2关代码变成了这样$str $_GET[keyword]; echo input typetext classform-control idname value.$str.;在第1关直接注入script标签的做法在这里行不通了因为输出位置变了——我们的输入被放在value...属性值里面。如果直接传scriptalert(1)/script浏览器只会把它当成input标签的value属性值的一部分脚本不会执行。这时候需要先闭合前面的双引号让我们的输入逃逸出属性值再构造新的标签或事件。先试最简单的闭合方式scriptalert(1)/scriptpayload里的闭合了value属性的左引号闭合了input标签后面的scriptalert(1)/script就成了独立的脚本标签可以正常执行。还有一种更优雅的解法是用事件属性不需要闭合标签 onfocusalert(1)把payload填进value属性后input标签变成input typetext idname value onfocusalert(1)只要input框获得焦点就会触发弹窗。实际测试中点一下输入框即可成功。第二种方法更贴近真实场景因为很多情况下标签括号会被过滤事件注入是比插入新标签更稳妥的绕过思路。2.3 第3关单引号闭合下的属性注入第3关的代码有一个明显变化$str $_GET[keyword]; echo input typetext idname value.$str.;value属性周围的引号从双引号变成了单引号。如果还用第2关的去闭合会发现闭合不掉因为这里的属性是用单引号包裹的在HTML属性里就是普通字符不起闭合作用。所以闭合字符要跟着输出位置变 onfocusalert(1)这样拼出来的HTML是input typetext idname value onfocusalert(1)同样点击input框触发弹窗。这一关最需要留意的思维转换是不要死记payload要观察输出位置的引号类型和标签结构。双引号属性就用双引号闭合单引号属性就用单引号闭合后面很多关卡的花式过滤都建立在这个基础上。还有一种常见的第3关解法是直接闭合后插入标签scriptalert(1)/script两种都能过但理解原理比记住哪一个能用更重要。2.4 第4关尖括号被过滤怎么办第4关明显开始上强度了。源码对输入做了处理$str $_GET[keyword]; $str2 str_replace(, , $str); $str3 str_replace(, , $str2); echo input typetext idname value.$str3.;str_replace函数依次把和替换成空字符串意味着script这种需要尖括号的标签型payload彻底失效。输入script进去出来就变成script标签结构被破坏。这时候“事件注入”的优势就体现出来了。事件属性payload根本不需要尖括号 onfocusalert(1)为什么会成功因为过滤规则只处理了尖括号没有处理单引号和onfocus事件而HTML标签属性本身就是可以执行JavaScript的。当输入框获得焦点时onfocus事件触发alert执行。这一关的深层启示是过滤了某些字符不等于过滤了执行途径。XSS的载体不只是script标签事件、伪协议、CSS表达式、SVG标签这些都是潜在的注入点。防御方如果只做简单的字符黑名单永远是堵不完的。2.5 第5关过滤script标签与双写绕过第5关的过滤逻辑又升级了$str strtolower($_GET[keyword]); $str2 str_replace(script, , $str); $str3 str_replace(/script, , $str2); echo input typetext idname value.$str3.;这关先做了strtolower把所有字母转小写然后过滤了script和/script。这意味着大写绕过和原始script标签都行不通了。第一种思路是改用不含script的标签比如img加onerror事件img srcx onerroralert(1)img标签加载图片失败时会触发onerror事件同样能执行JavaScript。但要注意这关源码在过滤前已经转小写了onerror事件本身没有被过滤所以能过。第二种思路是双写绕过scrscriptiptalert(1)/script为什么双写能绕过关键在于str_replace是单次替换从左到右扫描时匹配到script后替换为空但剩下的部分重新拼接后又组成了一个完整的script标签。比如scrscriptipt中间的script被删掉后剩下的是script。这一关建议两种payload都亲手试一遍感受一下“标签型”和“事件型”两种XSS在实战中是如何根据过滤规则进行取舍的。3. 第6关到第10关绕过过滤的进阶套路3.1 第6关大小写绕过第6关代码$str $_GET[keyword]; $str2 str_replace(script, , $str); $str3 str_replace(/script, , $str2); echo input typetext idname value.$str3.;注意对比第5关第6关没有strtolower。也就是说过滤规则只匹配小写的script和/script于是直接大小写混合就能绕过ScRiPtalert(1)/ScRiPtHTML标签名本身不区分大小写浏览器照样能把ScRiPt解析成script标签。这一关和上一关的对比非常经典同样的过滤规则多一个strtolower就让大小写绕过失效少一个就形同虚设。这给我们划了一个重点在分析过滤逻辑时先看有没有做大小写统一再做对应决策。很多防御方的思路是“黑名单 大小写归一化”但黑名单本身永远有遗漏。如果这关也想用事件注入那更简单 onfocusalert(1)事件注入根本不触碰script关键词只要贴合当前环境的属性结构就能过。越到后面的关卡越会发现事件注入是XSS绕过中非常通用的手段。3.2 第7关多关键词过滤与双写、编码混合第7关的过滤规则明显复杂了$str strtolower($_GET[keyword]); $str2 str_replace(/script, , $str); $str3 str_replace(script, , $str2); $str4 str_replace(javascript:, , $str3); $str5 str_replace(onerror, , $str4); echo $str5;这关同时过滤了script、/script、javascript:和onerror而且有strtolower大小写绕过直接失效。过滤顺序是从左到右依次执行的。先看有onerror事件怎么办。过滤规则是单次替换那我们就用双写img srcx onnerroralert(1)等等这里有个细节如果过滤的是onerror双写应该写成ononerrorerror还是onnerror原字符串是onerror过滤后变成空所以想在过滤之后剩下onerror需要在过滤前构造onnrror吗不对。正确的双写思路是把onerror写成onenerror。当str_replace把其中匹配到的onerror去掉后剩下的是onerroronenerror → 去掉第一个 onerroron error的部分 → 剩下 onerror我试过的经验是第7关最稳的payload有两种img srcx onnerroralert(1)这个思路是原字符串onnerror包含子串nerror其实不匹配。真正匹配的是onerror如果要过滤后完整剩下原始关键词更常见的是写成ononerrorerror这种双写形式。但实际操作中需要根据过滤函数的行为测试。另一种完全避开onerror和script的思路是用伪协议但javascript:也被过滤了。用java#x73;cript:这种实体编码绕过理论上也可以但这一关的上下文是value属性实体编码在属性解析时会被解码。不过第7关我个人实测最简单的方式还是双写插入scrscriptiptalert(1)/script因为script过滤是单次的scrscriptipt经过替换后重新组成script。这一关刷下来的体会是当多个关键词都被过滤时先逐一分析哪些过滤规则可以双写绕过哪些要用编码绕过不要指望一个payload通吃所有过滤。3.3 第8关JavaScript伪协议与实体编码绕过第8关的输出位置完全变了$str strtolower($_GET[keyword]); $str2 str_replace(javascript:, , $str); $str3 str_replace(;, , $str2); echo a href.$str3.链接/a;输入被放到a标签的href属性里并且javascript:和分号;都被过滤了。直接输入javascript:alert(1)会被替换成alert(1)href变成无效链接。这一关的关键在于理解HTML实体解码的时机。过滤发生在服务端字符串处理阶段而实体解码发生在浏览器解析HTML阶段。服务端看到的java#x73;cript:里面没有javascript:这个连续字符串所以过滤规则直接放行浏览器解析href属性时把#x73;解码成s得到的是javascript:alert(1)执行伪协议。最终payloadjava#x73;cript:alert(1)注意这里分号;被过滤了但#x73;必须要用分号结尾才能被正确解码。实测发现这关的过滤规则没有把实体编码的、#、x这些字符纳入黑名单所以能正常过。另外alert后面的分号会被过滤但不影响JavaScript执行因为alert函数调用语句后面不加分号也不会报错。第8关给我们的经验非常实用过滤黑名单只对原始字符串生效一旦浏览器存在解码过程HTML实体、URL编码、Unicode就可能出现解码后能执行但解码前不匹配的间隙。这类“双重解析”漏洞在真实Web应用中也经常出现。3.4 第9关强制包含http时的注释绕过第9关代码$str strtolower($_GET[keyword]); $str2 str_replace(javascript:, , $str); $str3 str_replace(;, , $str2); if (strpos($str3, http://) false) { echo 我真不明白你为什么就是不能好好的提交!; } else { echo a href.$str3.链接/a; }跟前一关类似但多了一个硬性条件payload里必须包含字符串http://否则直接拒绝输出。这意味着我们不能只写javascript:alert(1)还得想办法让http://混进payload里且不影响JavaScript执行。常见解法是利用JavaScript的注释符javascript:alert(1)//http:////在JavaScript里是单行注释所以//http://这部分不会被解释执行但它满足了strpos检测包含http://的条件。整个payload在输出后是a hrefjavascript:alert(1)//http://链接/a浏览器解析href时javascript:伪协议成立alert执行后面的//http://只是注释。还有一种写法javascript:alert(1)/*http://*/用块注释包住http://同样满足条件且不影响执行。实际测试单行注释写法足够通过。这一关的技巧核心是用注释符把“检测用的关键词”和“执行用的代码”隔离。检测逻辑看到的是整个字符串包含http://JavaScript引擎执行时看到的只是注释后面被忽略的多余内容。3.5 第10关POST型XSS与抓包实战前面9关全部是GET型直接改URL参数就能测。第10关开始变了$str $_POST[keyword]; echo input typetext idname value.$str.;参数从$_GET变成了$_POST直接改URL不行了因为URL里的参数进的是$_GET数组$_POST里根本拿不到。第一个能想到的办法是用Burp Suite抓包改请求。正常提交表单时拦截请求把GET改成POST把参数从URL挪到请求体里。具体操作浏览器先正常访问level10.phpBurp拦截到GET请求后右键Change Request Method改成POST然后在请求体里加keyword%27%20onfocus%3D%27alert(1)。第二个更轻量的办法是在浏览器控制台里用fetch发POST请求fetch(http://127.0.0.1/xsslabs/level10.php, { method: POST, headers: {Content-Type: application/x-www-form-urlencoded}, body: keyword onfocusalert(1) })页面刷新后input框里已经注入了onfocus事件点击输入框就能弹窗。这一关在真实场景中对应的是“存储型XSS和反射型XSS的输入点差异”。很多开发者防护了GET参数却忽略POST参数或者反过来所以测试时一定要覆盖所有请求方法。第10关的PHPSESSID等会话机制也提示我们POST型XSS更容易配合CSRF形成完整的攻击链。4. 刷靶场过程中的常见问题与排查方法4.1 弹不出框先别急三步定位问题刷靶场最让人抓狂的就是payload发出去页面安安静静一个弹窗都没有。多数情况下不是靶场坏了而是payload和输出位置不匹配。我总结了一个三步排查法每一步都在浏览器里验证。第一步先看输出位置。右键查看页面源代码搜索自己输入的某个特征字符串比如xss或test看它被渲染到了哪里——是在HTML标签内部、标签属性内部、JavaScript代码里还是被HTML编码成了lt;scriptgt;输出位置决定了闭合方式这一步错了后面全错。第二步看过滤规则对payload做了什么。把原始输入和源代码里显示的输出对比一下尖括号还在不在关键词被删了还是被转义了如果变成了lt;那是做了HTML实体编码事件注入也救不了如果只是把script删了那考虑双写或换事件。第三步确认事件能触发。用了onfocus要点击输入框用了onerror要让图片加载失败用了onmouseover要鼠标移上去。Payload本身没问题但触发条件没满足一样弹不出框。这一步最容易被新手忽略。4.2 工具与环境的几个坑刷题过程中环境问题也踩了不少。第一个坑是PHP版本差异。老版本靶场在新版PHP下可能出现警告或行为不一致最典型的例子是str_replace在传入数组时的表现差异、$_GET对同名参数的取值规则等。建议就用PHP 5.6到7.4之间太新的版本有时会有兼容性问题。第二个坑是浏览器的XSS过滤机制。Chrome对反射型XSS有内置拦截某些payload会被浏览器直接拦截不执行。遇到这种情况不要怀疑靶场出错了换成Firefox或者关闭对应安全特性再试。实际工作中各浏览器对XSS的防护策略确实不同这也是为什么渗透测试要随身备几款浏览器。第三个坑是URL编码问题。#、、、这些字符直接放进URL会被浏览器编码或截断。HackBar里的URL Encode功能就是干这个的。记住一个原则payload里的在URL中要写成%26空格要写成%20不然参数会被切碎。4.3 关键经验从通关到迁移一个非常值得做的练习是通关之后把payload放到前几关里试试。比如第7关的双写payload放到第4关会是什么结果第8关的实体编码放到第7关能过吗这些交叉测试能帮你快速建立对不同过滤规则组合的敏感度。另一个更进阶的练习是把每道题的payload迁移到DVWA或pikachu靶场的对应难度上。不同靶场对同一漏洞的过滤策略不一样但绕过思路是相通的。我的经验是真正把一个漏洞吃透的标志不是能过这个靶场而是换一个环境还能迅速找出注入点并构造出可用的payload。关卡输出位置核心过滤点最稳的绕过思路Level 1HTML标签内无过滤直接script标签Level 2双引号属性内无过滤双引号闭合 script标签/事件Level 3单引号属性内无过滤单引号闭合 事件注入Level 4单引号属性内过滤事件注入Level 5双引号属性内过滤script转小写script双写/事件注入Level 6双引号属性内过滤script无转小写大小写混合Level 7双引号属性内过滤script/onerror/javascript等双写绕过Level 8href属性内过滤javascript:和;HTML实体编码Level 9href属性内过滤javascript:并强制含http://注释符隔离Level 10POST参数属性内换用POST接收抓包改方法 事件注入5. 一点实操心得刷完这10关再回头看XSS漏洞的本质就是一个词信任。开发者信任了用户的输入把未经校验的数据直接拼进HTML浏览器信任了服务端返回的内容把看似正常的标签和属性全都解析执行。攻击者做的所有事情——闭合引号、双写关键词、实体编码绕过——都是在利用这份信任的裂缝。我个人在实际项目中最受用的不是某个具体payload而是那套“见招拆招”的思维方式先判断输出位置再分析过滤规则然后从标签注入、事件注入、伪协议、编码绕过这几个方向里选一个能走的路径。这套方法论就是你刷Xss-Labs最大的收获比记住一百个payload都值。后面几关还会涉及更多编码绕过和特殊上下文比如JavaScript代码块内的XSS那又是另一重难度了。等你把这套前10关的思维练熟再去碰存储型、DOM型XSS会发现很多问题是相通的。刷题愉快。