XSS跨站脚本攻击实战:Xss-Labs前十关通关思路与绕过技巧

发布时间:2026/9/25 13:39:50
XSS跨站脚本攻击实战:Xss-Labs前十关通关思路与绕过技巧 XSS跨站脚本攻击这个概念我当年看书看了三遍都没彻底转过弯来直到把Xss-Labs靶场前十关一关一关踩过去才真正理解什么叫用户输入不可信、输出不编码。这个靶场是专为XSS入门设计的本地训练环境关卡不算多但前十关恰好覆盖了从最基础的反射型弹窗到各种常见过滤策略的绕过手法非常有阶梯感。这篇文章就把我在Xss-Labs前10关的完整通关思路、payload和踩坑记录整理出来给刚接触Web安全、想系统吃透XSS原理的新手做参考也适合已经学过的朋友快速温习一遍代码层面的绕过逻辑。1. 为什么建议把Xss-Labs前十关完整过一遍先说个前提XSS在Web安全领域属于永远不能被忽视的老牌漏洞。它的本质是攻击者把恶意脚本注入到受害者浏览器页面中让受害者在信任站点上下文中执行这段脚本。学习它的意义不止于能弹个alert更在于理解浏览器到底怎么解析HTML、过滤器的盲区在哪里、数据在哪些环节可能发生语义变化。但直接拿真实网站练手既不合法也不道德靶场的价值就出来了。Xss-Labs是一个纯本地跑的开源练习项目所有payload都是在可控环境中测试随便折腾不用担心误伤。更关键的是它的关卡设计有意模拟了许多真实业务中常见的过滤写法有的关完全不设防有的关只过滤尖括号有的关搞黑名单替换有的关在href属性和URL校验上做文章。你把这十关打通实际上就把一套典型的过滤-绕过-再过滤-再绕过攻防链跑了一遍。这个靶场的难度坡度也很合理。第一关直接传参就能弹窗属于送你一血的友好开局中期的第三、四关开始逼你抛弃一定得用script标签的思维定式学会用事件属性后面的第五到第十关则逐个击破伪协议、大小写、Unicode偏爱和HTML实体编码等主题。整个过程我从只会照抄payload过渡到能自己根据源码写出payload这种手感上的变化看书是给不了的。2. 动手前的环境准备和三个前置认知2.1 把靶场跑起来Xss-Labs对环境要求不高一台能跑PHP的机器就行。我本地用的是PHP集成环境把靶场源码丢进Web根目录访问对应路径就能看到关卡入口。如果你更习惯用容器来跑把它做成镜像也很方便原理一致本质就是一个PHP网站。启动后建议先确认两件事第一URL参数能被正常请求和回显第二页面有没有报错提示。我曾经在Windows环境下遇到过PHP版本过高触发兼容警告的情况页面能出来但部分关卡过滤函数的行为怪怪的。真遇到这种问题优先检查PHP版本和相关扩展别急着怀疑自己payload写错了。2.2 过关的核心工具不是扫描器是浏览器F12很多新手过靶场喜欢去搜现成payload其实最该依赖的是浏览器开发者工具。按F12打开Element面板你能直接看到服务端输出的HTML长什么样当前payload是被原样拼进了标签内部还是被编码转义了一目了然。比如第二关输入普通文本后代码里会生成input namekeyword value你输入的内容这种结构。你输入script时浏览器可能会因为引号闭合问题把尖括号当成value的一部分显示出来而不会执行。这些细节全都需要靠Element面板去观察光在页面上看是看不到门道的。建议养成一个习惯每次提交payload后先看Element面板里最终被解析成的DOM结构再点开Console看有没有报错。如果payload没弹窗多半是闭合没闭合好或者某个引号把属性关进去了。2.3 浏览器解析XSS时的两个关键顺序想理解XSS payload为什么能生效得建立两个基础认知。第一个认知HTML属性拼接是字符串层面的。服务端代码里把用户输入直接拼进HTML字符串那么用户输入的引号和尖括号就会被浏览器当作HTML语法的一部分来解析。比如value?php echo $input; ?当输入是scriptalert(1)/script时最终DOM会被拆成多个节点而不是一个带value属性的input标签。这就是闭合-注入手法的由来。第二个认知HTML解析完成后浏览器才会执行JavaScript。更准确地说事件属性onclick、onfocus等天然就是一段JS代码的入口。所以只要注入点附近存在标签你未必需要构造新的script标签直接在老标签上借一个事件属性就够用了。后面前三、四关的解法全是这个思路。3. 第一二关零过滤下的直球注入和引号闭合3.1 Level 1 反射点直接在页面文本输入什么就输出什么第一关的注入点非常直白URL参数会直接拼到页面的文本内容里。这种场景在真实环境中对应的是搜索关键词回显、错误信息回显等位置没有经过任何HTML实体编码也没有标签过滤。直接用最基础的payload就能过关?namescriptalert(1)/script原因很简单服务端把这段字符串原样插入HTML的body中浏览器解析到script标签后就会把它当作脚本执行。这一关看着简单但它建立的反射点定位意识很重要。你拿到一个输入点第一件事永远是观察它出现在页面的哪个位置是标签内部、属性内部还是JavaScript代码内部位置不同payload构造思路完全不同。3.2 Level 2 输出进了value属性先闭合再注入第二关开始上强度了。这关的输入不再直接出现在页面文本里而是被塞进了input标签的value属性input namekeyword value这里是你输入的内容这时候直接输入script并不会执行因为尖括号会被当作value属性的字符串值而不是HTML标签的开始。我一开始也在这里卡过后来才反应过来既然value被双引号包着那就在双引号上做文章。先用闭合掉value属性再用闭合掉input标签让后续的script标签独立出来?keywordscriptalert(1)/script最终解析出来的DOM就是input namekeyword value scriptalert(1)/script第二关真正的考点不是XSS本身而是属性闭合。你去观察真实站点时会发现凡是把用户输入直接拼进标签属性的地方都可能存在这种问题。开发者最容易犯的错误是只过滤了尖括号却忘了引号也能改变标签的解析边界。4. 第三四关尖括号被过滤改走事件触发路线4.1 Level 3 闭合引号之后让已有标签承担触发任务到了第三关服务端开始过滤尖括号了和会被直接替换成空字符串。你没法再构造新的标签因为构造标签语法必需的和都没了。但注意它过滤的是尖括号并没有过滤引号也没有过滤标签内的事件属性。我的思路转向了借用已有的input标签先闭合value再在同一个标签上塞一个事件属性让这个标签自己在某个时刻触发JS。这里有个小细节不同教程里这关的payload有的用单引号闭合value有的用双引号闭合原因在于服务端输出时有不同的引号包裹方式。如果你打开Element面板发现value是双引号包裹的就用双引号闭合?keyword onfocusalert(1) autofocus最终DOM会变成input namekeyword value onfocusalert(1) autofocusautofocus会让页面加载后自动聚焦到这个input上聚焦事件触发onfocusalert就弹出来了。如果自动聚焦无效也可以换成onmouseover鼠标滑过时才触发。这两个事件一个偏自动一个偏交互按需使用。4.2 Level 4 同样是过滤尖括号但闭合方式变了第四关逻辑和第三关类似尖括号依然被过滤。区别在于它处理引号的方式不同输入的双引号可能原样保留也可能经过处理。常见通关payload是?keyword onmouseoveralert(1) 同样是先闭合value再注入事件。不过这个需要鼠标滑过input才会触发相比第三关的autofocus自动触发稍微笨一点。如果你想让第四关也自动弹完全可以改成?keyword onfocusalert(1) autofocus这一关给我的启发是过滤尖括号不代表安全只要属性还能够闭合事件属性依然是可利用的。而事件属性本身就是可执行代码入口这一点恰恰是很多新手最容易忽略的。4.3 过关技巧用F12快速判断该用哪种引号闭合第三四关最坑的地方是有人把单引号payload和双引号payload混着用。我的建议是提交一个普通测试字符比如xyz在Element面板里看它最终被插进标签时value属性到底是被单引号还是双引号包裹的。如果是valuexyz这种结构说明属性以双引号闭合你要用双引号来闭合如果单引号仍然在value里面说明闭合边界不是它。这个习惯非常实用。以后遇到任何属性型的注入点先搞清楚闭合符再动手别浪费时间试不同引号排列。5. 第五六关黑名单过滤下的标签替换与大小写绕过5.1 Level 5 过滤了script和on关键字改用伪协议标签第五关增加了一个黑名单会把script和on关键字删掉。这时候script标签已经走不通了onclick、onerror这类事件属性也因为包含on被同样过滤。不少新手到这里会卡住因为不能用script、不能用事件似乎把XSS的路都堵死了。但换个思路触发JS并不只有script这一种标签也未必需要通过事件属性。a标签的href属性天然支持javascript:伪协议点击链接时就会执行JS代码。payload可以这样构造?keyworda hrefjavascript:alert(1)点我/a需要注意这里的javascript:字符串并没有触发黑名单因为黑名单过滤的是script带尖括号这个整体而不是javascript中的script子串。此外href属性里也没有on关键字。所以这条payload能完整保留下来。当页面里出现一个可点击的点我链接点一下alert就触发了。第五关的价值在于让你认识javascript伪协议这一条XSS执行路径它是绕过script标签黑名单的经典手法。5.2 Level 6 大小写混合绕过只认小写的黑名单第六关的黑名单比第五关更全把script、on、src、data、href都加入了替换列表。乍看之下第五关的payload在第六关也活不成因为href被过滤了。但问题是这个过滤函数是大小写敏感的黑名单替换只匹配全小写形式。HTML属性名和javascript伪协议都不区分大小写于是大小写混合就能绕过?keyworda HrEfJaVaScRiPt:alert(1)点我/a在服务器端看来HrEf不是href所以没被删在浏览器看来HrEf就是href属性JaVaScRiPt就是javascript:伪协议。点击链接时alert照常弹出来。这场面的本质是字符串过滤和HTML语义解析遵循的是两套规则。过滤代码在字符串层面对关键字做字面匹配而浏览器在解析层面对大小写完全宽容。第一套规则会有第二套想象中的安全这是黑名单方案天生的隐患。6. 第七关删掉script关键字后的双写绕过第七关的黑名单对script字符串做了删除。它的过滤逻辑不是只删script而是直接把输入中的所有script这个子串删掉。如果你直接写scriptalert(1)/script它就变成alert(1)/一整个标签只剩个骨架。双写绕过的思路是既然你会删掉一次script那我就在script中间混进一个script让过滤函数删掉其中一个剩下的部分恰好拼回完整的s c r i p t。payload是这样构造的?keywordscrscriptiptalert(1)/scr/scriptipt我来拆解一下漏洞的配合过程原始字符串scrscriptipt里过滤函数从左往右匹配会先命中中间那个script子串并把它删除。删除后左边的scr和右边的ipt刚好拼接成script。闭合标签/scr/scriptipt同理删除中间的子串后拼接成/script。最终你传的payload在过滤后变成了scriptalert(1)/script双写之所以能成功关键是过滤逻辑只做一次、不带递归。如果写一个循环删除直到没有再出现目标串双写就失效了。这类删除后拼接出新危险字符串的模式同样出现在SQL注入的注释符绕过等场景里是一种通用的绕过思维。7. 第八九关href输出场景下的实体编码与URL校验7.1 Level 8 过滤了script子串就用HTML实体编码第八关把输出位置放到了a标签的href属性里页面结构大致是a href这里输出你输入的内容链接/a同时它会把输入中的script子串再次删掉。这时候你直接写javascript:alert(1)过滤后变成java:alert(1)伪协议失去了作用。真正的绕法是用HTML实体编码。浏览器在解析HTML属性时会先对属性的值做实体解码再把解码后的值当成href的实际地址。比如#x73;是字符s的十六进制实体#99;是c的实体#116;是t的实体。那么这样一条payload?keywordjavas#x63;ript:alert(1)在服务端做字符串替换时script并没有真的出现因为中间那个s变成了实体#x63;所以不会被过滤。浏览器解析时会把#x63;还原成s最终href的实际值依然是javascript:alert(1)。点击链接弹窗就出来了。这里我给你一个实用提示实体编码里的十六进制和十进制都可以不一定非要用#x73;#115;同样是s。你可以把任意一个字母编码掉不必整串编码过滤函数往往只是字符串匹配不会去做HTML实体解码。7.2 Level 9 既要包含http://又要执行JS一个注释符搞定第九关在第八关的基础上加了一条校验输入内容必须包含http://否则页面会提示不合法。这条校验的目的是限制payload必须像一条正常的URL比如https://example.com。但校验和实际使用之间的缝隙在于href属性真正执行JS时不要求整条URL只由协议和地址组成。你可以先用javascript:alert(1)作为可执行代码再用注释符把http://从JS逻辑中摘出去。payload如下?keywordjavas#x63;ript:alert(1)//http://在服务端实体#x63;让script躲过了关键字删除同时整串里确实带上了http://通过了合法性校验。在浏览器端href解析为javascript:alert(1)//http://JS引擎把//后的内容当作单行注释所以只执行alert(1)后面的http://没有任何影响。这一关充分说明了一个问题黑名单合法性校验的组合并不等于业务侧的安全闭环。规则与规则之间的缝隙恰恰是攻击者最喜欢的落脚点。8. 第十关隐藏表单域里的属性覆盖8.1 找到真正可控的注入点第十关的页面结构和前面不太一样。页面上除了搜索框底部还隐藏着几个隐藏的表单字段比如t_link、t_history、t_sort。如果你直接在keyword参数上传payload不一定能打到注入点因为服务端实际输出的是t_sort这个参数的值它被拼进了一个input typehidden namet_sort value...标签里。所以第一步是看源码、找参数名。用?t_sortxxx构造参数确认输出点位置。你会发现输入的内容出现在value属性的位置而且、等字符没有被过滤只是关键关键字可能被替换。8.2 给隐藏input换个显眼属性再用大小写事件收尾隐藏的input字段在页面上不可见也没法手动点击所以单纯闭合value注入一个事件是不够的——用户根本碰不到它。这时候可以利用HTML的后写属性覆盖先写属性机制同一标签里写两个相同的属性后面的生效。payload可以这样构造?t_sort typebutton OnClickalert(1)拼进页面后原始标签变成input typehidden namet_sort value typebutton OnClickalert(1)浏览器解析时后面这个typebutton覆盖了前面的typehidden原本隐藏的input变成可见按钮。虽然on关键字被过滤但过滤是大小写敏感的所以用OnClick能绕过。按钮出现后点一下就会触发alert。这一关非常有实战味道。真实表单里经常藏着大量隐藏字段它们因为看不见而被开发者忽视但只要某个隐藏字段接收了用户输入并回显到页面它同样可能成为XSS注入点。而同名属性覆盖这个技巧在篡改表单逻辑时也经常能派上用场。9. 十关通关后我对XSS过滤与防护的几点切身体会把十关全部跑完后我最大的感触是黑名单过滤这条路走到最后一定会遇到死角。做字符串替换的过滤函数永远面临编码、大小写、递归绕过、双写绕过等一整套针对策略。很多开发者以为屏蔽了script和on就安全了可XSS的触发方式远不止这么几种。伪协议、事件属性、实体编码任何一个变量没顾及到防御就会漏风。在防护侧更有价值的做法是输出编码与输入校验结合。凡是把数据输出到HTML上下文时都做HTML实体编码让、、引号在浏览器眼里只是普通文本而不是标签或属性边界如果需要用白名单就明确允许哪些标签、哪些协议而不是费劲去黑名单里堆词表。真实业务里我还会关注Content Security PolicyCSP尽量限制内联脚本的执行这样即使某个点被注入了一段代码CSP也能阻止它真正跑起来。另外说个很多人容易忽略的小点HttpOnly不是对抗XSS的直接手段但能降低XSS窃取Cookie的破坏力。如果会话Cookie带上了HttpOnly那么即使页面上能执行任意JS也无法通过document.cookie拿到会话标识。这不能阻止弹窗但能在真实攻击场景里减少伤害。防御从来不是单点就能解决的而是要层层叠加。最后一关通过之后我把这些payload都整理成了一个测试列表日常做代码审计或者写过滤器的时候会直接拿这批用例去跑一遍看新的防护方案是否存在同样的绕过口子。学习XSS最后学到的不只是几个payload而是一种任何输入都可能被重新解释的思维习惯。这套习惯是靶场送给我们最值钱的东西。