1. 从零理解XSS闯关的核心逻辑很多人第一次接触XSS闯关平台时习惯性地把它当成填空题——看到输入框就塞scriptalert(1)/script弹窗不出来就换下一个payload完全靠运气试。这种打法在低难度关卡偶尔能蒙混过关但到了中高难度就会彻底卡死因为你根本不知道过滤规则在拦什么、浏览器在怎么解析、服务端在怎么处理。XSS闯关的本质不是背payload而是理解一条完整的攻击链路用户输入 → 服务端处理 → 前端渲染 → 浏览器解析执行。每一环都可能存在过滤、编码、转义而你要做的就是找到那条输入能穿透所有关卡、最终被浏览器当作代码执行的路径。这跟渗透测试中找SQL注入的思路是一回事——先判断上下文再决定用什么语法闭合最后构造出可执行的语句。这篇文章适合三类人看刚入门Web安全、想系统刷一遍XSS闯关的新手刷了一半卡住、想搞懂每关背后原理的中级选手以及想把这些关卡当作教学素材、需要一套完整解题思路的安全讲师。我会按照关卡难度递进的顺序把每一关的上下文类型、过滤机制、绕过思路、最终payload拆开讲清楚同时补充大量实战中才会遇到的细节比如浏览器解析优先级、编码绕过、DOM型XSS的特殊性等。需要提前说明的是XSS闯关平台的设计目的是教学所有关卡都是刻意构造的漏洞环境。你在真实项目中遇到的情况会复杂得多——有WAF、有CSP、有HttpOnly、有各种前端框架的自动转义。但闯关训练的价值在于它帮你建立起看到输入点就能条件反射地判断上下文的能力这个能力在实战中是无价的。2. 反射型XSS的上下文判断与闭合技巧2.1 第一关没有任何过滤的送分题第一关通常是这样的页面有一个搜索框你输入什么它就原样显示在页面上没有任何过滤。很多人觉得这关太简单直接alert(1)就过了但我想说的是这关真正的价值在于让你建立上下文判断的意识。你输入test页面显示你搜索的内容是test。这时候你要问自己我的输入出现在HTML的什么位置是在标签之间div这里/div还是在标签属性里input value这里还是在JavaScript代码里var a 这里位置不同闭合方式完全不同。第一关的输入出现在标签之间所以最直接的payload就是scriptalert(1)/script但这里有个细节很多人忽略现代浏览器对于通过innerHTML插入的script标签是不会执行的。如果这关是用innerHTML渲染的你写script可能没反应。这时候要改用事件型payloadimg srcx onerroralert(1)img标签的src加载失败时会触发onerror事件这个事件在innerHTML插入时依然会执行。这是反射型XSS中最常用的万能payload之一建议背下来。提示判断script是否被执行可以看页面是否真的弹窗。如果没弹先别急着换payload打开F12看看你的输入被插入到了DOM的哪个位置这比盲目试payload高效得多。2.2 第二关value属性里的闭合陷阱第二关的输入通常出现在input标签的value属性里类似这样input value你的输入这时候你直接写scriptalert(1)/script是没用的因为它会被当作value的一部分变成input valuescriptalert(1)/script浏览器会把整个东西当作属性值不会执行。正确的思路是先闭合属性再注入标签。你需要构造scriptalert(1)/script这样渲染出来就是input valuescriptalert(1)/script属性被闭合后面的script就成了独立的标签。但同样的问题如果这关用innerHTML渲染script不执行那就换成img srcx onerroralert(1)这里有个容易踩的坑有些关卡会把转义成quot;这时候你闭合不了属性。要试试单引号或者看看能不能用其他方式闭合。还有一种情况是属性没有用引号包裹比如input value你的输入这时候你甚至不需要闭合引号直接空格加事件就行x onmouseoveralert(1)2.3 第三关单引号与HTML实体编码的博弈第三关往往会把双引号转义但单引号没处理。输入出现在类似这样的位置input value你的输入这时候你用双引号闭合没用得用单引号img srcx onerroralert(1)但有些关卡更狠它会把和都转义成lt;和gt;这时候你注入不了新标签。怎么办答案是利用现有标签的事件属性。比如输入出现在input的value里你可以这样 onmouseoveralert(1)渲染出来就是input value onmouseoveralert(1)你不需要注入新标签只需要给现有标签加一个事件属性。用户鼠标划过输入框时就触发。这种思路在实战中非常有用因为很多WAF只拦script和img对事件属性反而宽松。2.4 第四关双写绕过与大小写混淆到了第四关过滤开始升级。常见的过滤方式是把script替换成空字符串。比如你输入script服务端把script删掉变成payload就废了。绕过思路是双写scrscriptiptalert(1)/scrscriptipt服务端把中间的script删掉后剩下的拼起来正好是scriptalert(1)/script。这是最经典的绕过手法之一。另一种是大小写混淆ScRiPtalert(1)/ScRiPt如果服务端只匹配小写的script大小写混合就能绕过。但要注意HTML标签本身是大小写不敏感的浏览器能正常解析。还有一种情况是过滤了alert这时候可以用confirm、prompt或者用eval拼接img srcx onerroreval(alert(1))2.5 第五关a标签href里的javascript协议第五关的输入通常出现在a href你的输入里。这时候你注入script没用因为href属性里不能放标签。正确的做法是用javascript:协议javascript:alert(1)点击链接时就执行。但很多关卡会过滤javascript这个词绕过方式有大小写混合JaVaScRiPt:alert(1)插入空白字符java\tscript:alert(1)某些浏览器支持HTML实体编码java#115;cript:alert(1)如果javascript被完全过滤还可以试试data:协议data:text/html,scriptalert(1)/script不过data:协议在现代浏览器中限制较多不一定能执行脚本。3. 存储型XSS从留言板到持久化攻击3.1 存储型与反射型的本质区别反射型XSS是你输入什么页面立刻显示什么攻击是一次性的需要诱导受害者点击特制链接。存储型XSS则是你输入的内容被存到数据库所有访问这个页面的用户都会中招。这个区别决定了存储型XSS的危害等级要高得多——你不需要诱导只要有人访问被污染的页面就会触发。在闯关平台中存储型XSS通常出现在留言板、评论区、用户昵称、个人签名这些地方。解题思路和反射型类似但要多考虑一层你的输入会被存进数据库取出来的时候可能经过转义。所以你要测试的是存入时过滤还是取出时过滤还是两者都有。3.2 留言板关卡的完整测试流程假设有一个留言板你输入昵称和内容提交后页面显示所有留言。测试流程应该是这样的第一步输入普通文本test123提交后查看页面源码确认你的输入出现在什么位置。是直接输出在HTML里还是经过转义第二步输入btest/b看页面是否加粗。如果加粗了说明没有转义可以注入标签。如果显示的是字面的btest/b说明被转义了。第三步输入scriptalert(1)/script看是否弹窗。如果不弹换成img srcx onerroralert(1)。第四步如果都不行检查是不是只过滤了某些标签。试试svg onloadalert(1)、body onloadalert(1)、details open ontogglealert(1)这些冷门标签。这里有个实战经验存储型XSS的payload要考虑持久性。你提交的payload会被存进数据库如果payload里有特殊字符导致SQL报错可能存不进去。所以尽量用简单的payload确认能弹窗后再逐步升级。3.3 存储型XSS的隐蔽性与危害放大存储型XSS最可怕的地方在于它的隐蔽性。反射型XSS的URL里带着明显的payload稍微有点安全意识的人都能看出来。但存储型XSS的URL是干净的受害者访问的是正常页面却在不知不觉中执行了攻击者的脚本。在闯关平台中存储型XSS关卡通常会模拟一个后台管理页面。你作为普通用户提交留言管理员查看留言时触发XSS你的脚本就能读取管理员的cookie或者以管理员身份执行操作。这就是XSS最经典的利用链存储型XSS 后台 越权。在实战中存储型XSS的利用方式包括窃取cookie如果没设HttpOnly构造CSRF请求以受害者身份执行操作键盘记录窃取敏感信息钓鱼弹出伪造的登录框注意闯关平台中的cookie窃取通常需要配合一个接收服务器。在本地测试时你可以用alert(document.cookie)来验证不需要真的搭建接收端。4. DOM型XSS不经过服务端的攻击面4.1 DOM型XSS的独特之处DOM型XSS和前面两种最大的区别是payload不经过服务端。整个攻击过程发生在浏览器端服务端返回的HTML是干净的是前端JavaScript代码把用户输入拼接到了DOM里导致了XSS。典型的DOM型XSS代码长这样var name location.hash.substring(1); document.getElementById(output).innerHTML name;你访问page.html#img srcx onerroralert(1)JavaScript从hash里取出payload用innerHTML插入页面触发XSS。服务端完全不知道发生了什么。4.2 定位DOM型XSS的source和sink解DOM型XSS关卡关键是找到source输入来源和sink危险输出点。常见的source有location.hashlocation.searchdocument.referrerwindow.namepostMessage的数据常见的sink有innerHTMLouterHTMLdocument.writeevalsetTimeout/setInterval传入字符串时Function构造函数在闯关平台中你可以打开F12在Sources面板里搜索这些关键词找到拼接逻辑。比如你搜innerHTML发现某段代码把location.search的值直接赋给了innerHTML那这就是漏洞点。4.3 DOM型XSS的绕过与利用DOM型XSS的绕过思路和反射型类似但要注意一点payload可能经过URL编码。比如location.search取出来的值是编码后的你需要用decodeURIComponent解码。在构造payload时要确保编码后的形式能正确传递。举个例子如果代码是var q decodeURIComponent(location.search.substring(1)); document.getElementById(result).innerHTML q;你访问page.html?img srcx onerroralert(1)浏览器会自动把、编码成%3C、%3EdecodeURIComponent解码后还原成img触发XSS。但如果代码没有decodeURIComponent你就需要手动编码payload确保取出来的是可执行的HTML。DOM型XSS还有一个特殊点它可能不依赖用户交互。比如innerHTML插入的img onerror会自动触发不需要用户点击。而javascript:协议需要点击。在闯关时优先用自动触发的payload。5. XSS闯关中的过滤绕过与编码技巧5.1 常见过滤规则与对应绕过表在闯关过程中你会遇到各种各样的过滤规则。下面这张表整理了最常见的过滤方式和对应的绕过思路建议收藏过滤规则绕过思路示例payload过滤script用事件型标签img srcx onerroralert(1)过滤script关键词双写或大小写scrscriptipt或ScRiPt过滤alert用其他函数或拼接confirm(1)或eval(alert(1))过滤和利用现有标签事件 onmouseoveralert(1)过滤引号用反引号或不用引号img srcx onerroralert(1)过滤空格用/或%09img/srcx/onerroralert(1)过滤on事件用其他事件或标签svg onloadalert(1)过滤括号用throw或模板字符串svg onloadalert\15.2 HTML实体编码与URL编码的实战运用编码是绕过过滤的核心武器。你需要搞清楚三种编码的区别和适用场景HTML实体编码把变成lt;变成gt;变成quot;。这种编码在HTML解析时会被还原。如果你能控制输出点可以用实体编码绕过关键词过滤。比如javascript可以写成java#115;cript。URL编码把变成%3C变成%3E。这种编码在URL传递时使用服务端或前端解码后还原。在DOM型XSS中特别常用。JavaScript编码把字符变成\x3c、\u003c这种形式。在JavaScript代码里执行时还原。如果payload出现在script标签内部可以用这种编码。实战中经常需要多重编码。比如服务端解码一次前端再解码一次你就要编码两次。这个需要根据具体关卡测试没有万能公式。5.3 浏览器解析优先级与payload构造浏览器解析HTML有一套优先级规则理解这个规则能帮你构造更可靠的payload。比如script标签内的内容不会被当作HTML解析所以里面不能放HTML标签属性值里的内容不会被当作标签解析除非你闭合了属性注释!-- --里的内容不会被解析但可以用--提前闭合注释有个经典技巧是利用!--和--来绕过某些过滤。比如你的输入出现在注释里!-- 你的输入 --你可以输入--scriptalert(1)/script!--来闭合注释并注入脚本。另一个技巧是利用标签的嵌套规则。比如title标签里的内容不会被解析为HTML但你可以用/title闭合它然后注入脚本。6. 从闯关到实战XSS防御与检测的逆向思维6.1 防御者视角XSS的根治方案刷完闯关你应该反过来想如果我是开发者怎么防住这些攻击答案其实很明确第一输出编码。根据输出位置选择正确的编码方式。HTML内容用HTML实体编码属性用属性编码JavaScript用JS编码URL用URL编码。这是最根本的防御手段。第二输入验证。对用户输入做白名单校验比如昵称只允许字母数字邮箱必须符合格式。但输入验证不能替代输出编码因为攻击者可以绕过前端验证直接发请求。第三CSP内容安全策略。通过HTTP头限制页面能加载哪些资源、能执行哪些脚本。CSP是最后一道防线即使有XSS漏洞攻击者也可能因为CSP而无法执行脚本。第四HttpOnly Cookie。给敏感cookie加上HttpOnly标记JavaScript就读不到即使XSS成功也偷不走cookie。6.2 检测者视角如何快速发现XSS漏洞在渗透测试中发现XSS漏洞的效率取决于你的测试方法。我的经验是第一步找所有输入点。URL参数、表单字段、HTTP头Referer、User-Agent、Cookie、postMessage这些都是潜在的source。第二步用唯一标记测试。输入一个独特的字符串比如xss123test然后在页面源码里搜索这个字符串看它出现在哪里、有没有被转义。第三步根据上下文构造payload。如果出现在标签之间试img srcx onerroralert(1)如果出现在属性里试闭合属性如果出现在JavaScript里试闭合引号或字符串。第四步用自动化工具辅助。Burp Suite的Scanner、XSStrike、DalFox这些工具能帮你快速筛选但最终验证还是要手动。6.3 闯关训练的真正价值很多人刷完XSS闯关就觉得自己会XSS了其实差得远。闯关平台的环境是理想化的没有WAF、没有CSP、没有前端框架的自动转义。真实项目中你可能遇到React/Vue的自动转义让你根本注入不了严格的CSP只允许加载同源脚本WAF的语义分析能识别出变形后的payload各种编码嵌套让你搞不清到底解码了几层但闯关训练的价值在于它帮你建立了条件反射式的上下文判断能力。你看到一个输入点脑子里会自动浮现出这是HTML上下文还是属性上下文还是JS上下文然后自动匹配对应的payload。这个能力是实战的基础没有它你在真实项目中就是无头苍蝇。我的建议是闯关刷一遍就够了重点是理解每关的原理。然后去真实的漏洞平台比如DVWA、Pikachu练习再然后去SRC或众测项目里实战。只有经历过真实环境的毒打你才算真正掌握了XSS。最后分享一个我在实战中总结的小技巧遇到过滤不要慌先把过滤规则摸清楚。你可以输入、、、、script、alert这些关键词看哪些被过滤了、哪些没被过滤。然后根据过滤规则反推绕过方案。这个过程比盲目试payload高效十倍。