news 2026/10/17 11:13:17

XSS闯关实战指南:从反射型到DOM型的绕过技巧与防御思维

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
XSS闯关实战指南:从反射型到DOM型的绕过技巧与防御思维

1. 从零理解XSS闯关的核心逻辑

很多人第一次接触XSS闯关平台时,习惯性地把它当成"填空题"——看到输入框就塞<script>alert(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就是:

<script>alert(1)</script>

但这里有个细节很多人忽略:现代浏览器对于通过innerHTML插入的<script>标签是不会执行的。如果这关是用innerHTML渲染的,你写<script>可能没反应。这时候要改用事件型payload:

<img src=x onerror=alert(1)>

<img>标签的src加载失败时会触发onerror事件,这个事件在innerHTML插入时依然会执行。这是反射型XSS中最常用的"万能payload"之一,建议背下来。

提示:判断<script>是否被执行,可以看页面是否真的弹窗。如果没弹,先别急着换payload,打开F12看看你的输入被插入到了DOM的哪个位置,这比盲目试payload高效得多。

2.2 第二关:value属性里的闭合陷阱

第二关的输入通常出现在<input>标签的value属性里,类似这样:

<input value="你的输入">

这时候你直接写<script>alert(1)</script>是没用的,因为它会被当作value的一部分,变成:

<input value="<script>alert(1)</script>">

浏览器会把整个东西当作属性值,不会执行。正确的思路是先闭合属性,再注入标签。你需要构造:

"><script>alert(1)</script>

这样渲染出来就是:

<input value=""><script>alert(1)</script>">

属性被"闭合,后面的<script>就成了独立的标签。但同样的问题,如果这关用innerHTML渲染,<script>不执行,那就换成:

"><img src=x onerror=alert(1)>

这里有个容易踩的坑:有些关卡会把"转义成&quot;,这时候你闭合不了属性。要试试单引号',或者看看能不能用其他方式闭合。还有一种情况是属性没有用引号包裹,比如<input value=你的输入>,这时候你甚至不需要闭合引号,直接空格加事件就行:

x onmouseover=alert(1)

2.3 第三关:单引号与HTML实体编码的博弈

第三关往往会把双引号转义,但单引号没处理。输入出现在类似这样的位置:

<input value='你的输入'>

这时候你用双引号闭合没用,得用单引号:

'><img src=x onerror=alert(1)>

但有些关卡更狠,它会把<和>都转义成&lt;和&gt;,这时候你注入不了新标签。怎么办?答案是利用现有标签的事件属性。比如输入出现在<input>的value里,你可以这样:

' onmouseover='alert(1)

渲染出来就是:

<input value='' onmouseover='alert(1)'>

你不需要注入新标签,只需要给现有标签加一个事件属性。用户鼠标划过输入框时就触发。这种思路在实战中非常有用,因为很多WAF只拦<script>和<img>,对事件属性反而宽松。

2.4 第四关:双写绕过与大小写混淆

到了第四关,过滤开始升级。常见的过滤方式是把script替换成空字符串。比如你输入<script>,服务端把script删掉,变成<>,payload就废了。

绕过思路是双写:

<scrscriptipt>alert(1)</scrscriptipt>

服务端把中间的script删掉后,剩下的拼起来正好是<script>alert(1)</script>。这是最经典的绕过手法之一。

另一种是大小写混淆:

<ScRiPt>alert(1)</ScRiPt>

如果服务端只匹配小写的script,大小写混合就能绕过。但要注意,HTML标签本身是大小写不敏感的,浏览器能正常解析。

还有一种情况是过滤了alert,这时候可以用confirm、prompt,或者用eval拼接:

<img src=x onerror=eval('ale'+'rt(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,<script>alert(1)</script>

不过data:协议在现代浏览器中限制较多,不一定能执行脚本。

3. 存储型XSS:从留言板到持久化攻击

3.1 存储型与反射型的本质区别

反射型XSS是"你输入什么,页面立刻显示什么",攻击是一次性的,需要诱导受害者点击特制链接。存储型XSS则是"你输入的内容被存到数据库,所有访问这个页面的用户都会中招"。这个区别决定了存储型XSS的危害等级要高得多——你不需要诱导,只要有人访问被污染的页面就会触发。

在闯关平台中,存储型XSS通常出现在留言板、评论区、用户昵称、个人签名这些地方。解题思路和反射型类似,但要多考虑一层:你的输入会被存进数据库,取出来的时候可能经过转义。所以你要测试的是"存入时过滤"还是"取出时过滤",还是两者都有。

3.2 留言板关卡的完整测试流程

假设有一个留言板,你输入昵称和内容,提交后页面显示所有留言。测试流程应该是这样的:

第一步,输入普通文本test123,提交后查看页面源码,确认你的输入出现在什么位置。是直接输出在HTML里,还是经过转义?

第二步,输入<b>test</b>,看页面是否加粗。如果加粗了,说明没有转义,可以注入标签。如果显示的是字面的<b>test</b>,说明被转义了。

第三步,输入<script>alert(1)</script>,看是否弹窗。如果不弹,换成<img src=x onerror=alert(1)>。

第四步,如果都不行,检查是不是只过滤了某些标签。试试<svg onload=alert(1)>、<body onload=alert(1)>、<details open ontoggle=alert(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 src=x onerror=alert(1)>,JavaScript从hash里取出payload,用innerHTML插入页面,触发XSS。服务端完全不知道发生了什么。

4.2 定位DOM型XSS的source和sink

解DOM型XSS关卡,关键是找到source(输入来源)和sink(危险输出点)。常见的source有:

  • location.hash
  • location.search
  • document.referrer
  • window.name
  • postMessage的数据

常见的sink有:

  • innerHTML
  • outerHTML
  • document.write
  • eval
  • setTimeout/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 src=x onerror=alert(1)>,浏览器会自动把<、>编码成%3C、%3E,decodeURIComponent解码后还原成<img>,触发XSS。

但如果代码没有decodeURIComponent,你就需要手动编码payload,确保取出来的是可执行的HTML。

DOM型XSS还有一个特殊点:它可能不依赖用户交互。比如innerHTML插入的<img onerror>会自动触发,不需要用户点击。而javascript:协议需要点击。在闯关时,优先用自动触发的payload。

5. XSS闯关中的过滤绕过与编码技巧

5.1 常见过滤规则与对应绕过表

在闯关过程中,你会遇到各种各样的过滤规则。下面这张表整理了最常见的过滤方式和对应的绕过思路,建议收藏:

过滤规则绕过思路示例payload
过滤<script>用事件型标签<img src=x onerror=alert(1)>
过滤script关键词双写或大小写<scrscriptipt>或<ScRiPt>
过滤alert用其他函数或拼接confirm(1)或eval('ale'+'rt(1)')
过滤<和>利用现有标签事件' onmouseover='alert(1)
过滤引号用反引号或不用引号<img src=x onerror=alert(1)>
过滤空格用/或%09<img/src=x/onerror=alert(1)>
过滤on事件用其他事件或标签<svg onload=alert(1)>
过滤括号用throw或模板字符串<svg onload=alert\1`>`

5.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标签
  • 属性值里的内容不会被当作标签解析,除非你闭合了属性
  • 注释<!-- -->里的内容不会被解析,但可以用-->提前闭合注释

有个经典技巧是利用<!--和-->来绕过某些过滤。比如你的输入出现在注释里:

<!-- 你的输入 -->

你可以输入--><script>alert(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 src=x onerror=alert(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高效十倍。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 11:11:10

Agentic AI 工程落地:从“能对话”到“能干活”的完整链路

Agentic AI学习笔记&#xff08;3&#xff09;&#xff1a;从“能对话”到“能干活”的完整链路写这个系列的第三篇时&#xff0c;我特意回看了前两篇的笔记边界。第一篇聊的是Agentic AI的基本概念&#xff0c;第二篇重点拆了智能体的规划能力&#xff0c;而这篇我想把视角拉到…

作者头像 李华
网站建设 2026/10/17 11:09:47

综合网络实验心得:BGP、VRRP、MSTP与路由重分发的协同实践

实验室里搭完一个包含BGP、DHCP中继、VRRP、MSTP、路由重分发等十余项技术的综合网络&#xff0c;最深的体会是&#xff1a;教科书上的知识点是一个个孤岛&#xff0c;而综合实验就是那座把它们连起来的桥。这篇文章就聊聊我做完这个实验后的一些心得&#xff0c;从整体设计思路…

作者头像 李华
网站建设 2026/10/17 11:09:45

PHP WebSocket聊天实例:从握手到广播的完整实现

简介&#xff1a;这是一份面向PHP后端初学者与实时通信开发者的WebSocket聊天实例源码&#xff0c;聚焦于用PHP搭建可运行的即时通讯服务&#xff0c;解决长连接建立、消息收发与广播等入门难点。压缩包共3个文件&#xff0c;包含2个php与1个docx&#xff0c;前者分别承担服务端…

作者头像 李华
网站建设 2026/10/17 11:09:05

双AI协同写作实战:Claude Code与Codex交叉审稿的论文创作流程

把“claude code、codex双AI协同论文写作”这件事讲清楚&#xff0c;我决定不绕弯子。这两个工具&#xff0c;一个叫claude code、一个叫codex&#xff0c;本来都是给程序员写代码用的终端助手&#xff0c;但我这几个月一直在用它们干一件看起来不太沾边的事——写论文。不是让…

作者头像 李华
网站建设 2026/10/17 11:07:33

LLM应用开发五层架构:从模型接入到交互体验的实战指南

这几年我接触了不少LLM应用开发项目&#xff0c;也见过太多团队卡在同一个位置上&#xff1a;API调通了&#xff0c;Demo也能聊&#xff0c;朋友看完都说“哇塞”&#xff0c;可一旦认真做产品&#xff0c;立刻开始原地打转——回答胡说八道、上下文一长就失忆、接了知识库还是…

作者头像 李华
网站建设 2026/10/17 11:06:14

TCP协议聊天与文件传输系统设计:从socket到粘包拆包实战

很多做网络编程的朋友第一次认真写的项目&#xff0c;都绕不开“聊天软件”这个东西。项目标题叫“基于TCP协议的聊天与文件传输系统设计与实现”&#xff0c;名字听着像课程设计&#xff0c;但真正做完你会发现&#xff0c;这一个小项目把网络编程里最容易踩坑的几个地方全串起…

作者头像 李华