news 2026/10/22 6:00:53

CTFSHOW XSS入门系列通关记录:从反射型到DOM型的绕过实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CTFSHOW XSS入门系列通关记录:从反射型到DOM型的绕过实战

最近抽空把CTFSHOW的XSS入门系列从web316刷到了web333,一共18道题,前后花了三个晚上。老实说,这套题作为XSS新手入口非常合适,难度曲线拉得相当平缓,没有一上来就扔混淆加密那种劝退操作,而是从最基础的反射型弹窗开始,一点点把过滤、绕过、DOM型这些概念串起来。如果你正准备入门XSS,或者刷题卡在某个绕过点上,这篇记录应该能给你省下不少时间。

先说下整体感受:前几题基本是给新人找自信的,payload直接写<script>alert(1)</script>就能过;中间开始上过滤,逼着你去了解HTML标签白名单、事件属性、编码这些底层知识;最后几题已经需要认真扒前端源码、分析DOM渲染逻辑了。整个过程很像搭积木,每一题都在上一题的基础上加一点新东西,只要你不眼高手低,按顺序刷下来收获会非常扎实。

1. 题目编排与XSS学习路线拆解

1.1 为什么XSS适合作为CTF Web入门口

很多新人刚接触CTF Web时,容易被SQL注入、文件上传这些题目的“一把梭”风格带偏,以为找个工具扫一扫就能出flag。但XSS这条路不一样,它几乎不依赖自动化工具,更多是考验你对前端执行逻辑、浏览器解析规则、服务端过滤策略的理解。web316到web333这一串题,正好把XSS最核心的几个知识点覆盖了一遍:反射型、存储型、DOM型、关键字过滤、标签黑名单、事件属性、编码绕过、长度限制。

刷完这套题,你得到的不是某个工具的熟练度,而是“看到一个输入点,能在脑子里快速跑一遍可能被利用的方式”这种思考习惯。这个习惯对后面接触CSRF、CSP绕过、甚至前端框架审计都有直接帮助。跟爆破、SQL注入相比,XSS入门更贴近真实业务场景里的逻辑漏洞,你以后看任何前端页面,都会下意识地盯住innerHTML、document.write、location这类危险函数。

1.2 web316-333的整体难度梯队

我在刷的时候,把18道题分了三个阶段,每阶段的侧重点完全不同。

第一阶段大概是web316到web321,纯粹是反射型XSS的填鸭教学。你提交的参数会被服务端拼进页面,然后原样输出,没有任何过滤。这个阶段的目的是让你把“输入点在哪里”“输出点在哪里”“如何判断漏洞存在”这三个基础问题彻底搞清楚。我自己的做法是每次都先提交一个普通字符串,比如hello,然后在浏览器里看它出现在什么位置,再决定怎么构造payload。

第二阶段是web322到web328左右,开始出现过滤。有的题把script替换成空字符串,有的题把大小写混写的<Script>原样输出但小写会被吞,还有些题开始限制可用的HTML标签。到这个阶段,只靠一句<script>已经到处碰壁,你需要系统学习HTML标签和事件属性的配对关系:img配onerror、svg配onload、a配onclick、details配ontoggle。这个过程非常锻炼记忆力,刷完再看到一个被过滤的页面,脑子里会自然浮现出好几条备选路线。

第三阶段是web329到web333,难度明显上来。DOM型XSS开始作为主角登场,你需要在浏览器开发者工具里手动追JavaScript代码,找document.write或者innerHTML的赋值来源。有些题还加了长度限制、闭合条件、编码干扰,需要你像拼图一样把payload一点一点拼出来。到这个阶段你已经不能靠背payload通关了,必须真的理解了浏览器在渲染HTML时“先解析实体、再识别标签、再触发事件”的顺序。

2. 三类XSS的核心识别方法与构造逻辑

2.1 反射型XSS:跟着参数找输出

反射型XSS最典型的特征是“一次性的”,你提交的payload会立刻在响应里体现,但不会保存到数据库。在CTF里,这类题基本都是GET参数进、HTML输出出。我最常用的检测流程是三步:先输入一个唯一标记,比如qwer1234;然后在响应页面里全局搜索这个标记;看它被放在哪一层标签里。

这里有个特别容易忽略的细节:输出点的位置决定了payload的闭合方式。如果标记被放在<div>标签的文本内容里,那直接写<script>就行;但如果被放在HTML标签的属性值里,比如<input value="这里">,那就必须先闭合双引号和标签,变成"><script>alert(1)</script>。web316-318阶段实际上就在反复训练这个闭合思路,只是很多新手没意识到,以为自己只是一路“背题”刷过去的。

2.2 存储型XSS:利用起来通常要过两道关

存储型XSS在入门题里往往设计得比反射型稍微复杂一点,因为payload会被服务端接收并保存,下次访问时再输出。CTFSHOW这套题里,存储型XSS的出题风格偏“留言板”套路:一个输入框,一个展示区,服务端不设过滤。难点不在注入本身,而在于你需要理解存储型XSS的触发路径是“用户A提交→服务端存储→用户B访问时执行”。

刷题时我习惯在payload里加一个时间戳注释,比如<script>alert(1)//20240327</script>,这样如果页面输出了我的payload,我能直观看到它是不是已经落库了。另外提醒一句:存储型XSS的flag校验往往不是立即弹窗,而是需要你再触发一次页面加载,别因为第一次没反应就怀疑payload写错了。

2.3 DOM型XSS:服务端碰都没碰你的输入

DOM型XSS是这套题里最容易让新手摔跤的题型,因为它不走服务端渲染,而是前端JavaScript直接操作DOM导致XSS。比如document.write(location.search)或者element.innerHTML = location.hash,你的输入在代码里被当作HTML片段解释执行了。

判断DOM型XSS有个很实用的土办法:用浏览器开发者工具打开Network面板,刷新页面看有没有产生新的网络请求。如果我的输入压根没出现在请求参数里,但页面渲染却受我控制的字符串影响,那大概率就是DOM型。刷这类题时,不要急着拼payload,先稳住心态把前端JS从头到尾读一遍,找到那张“危险函数调用链”。web331之后有几题就是典型:一个window.location参数经过两次拼接,最后落进innerHTML,你光靠试是试不出来的,必须得看出来“参数经过了哪几个函数、被加工成了什么格式”。

2.4 构造payload的通用骨架

不管哪种XSS,一个完整的payload基本逃不出这个骨架:闭合上下文 + 触发标签 + 事件属性 + 执行函数。举例,如果参数输出在<div>这里</div>,那骨架是<script>alert(1)</script>;如果输出在<a href="这里">,那骨架是"><img src=x onerror=alert(1)>。

关键是要记住:闭合上下文是整个payload的生命线,闭合失败,后面全是白搭。我刷题时会反复利用开发者工具的Elements面板观察当前DOM节点的高亮范围,这比盲猜靠谱得多。

3. 通关实录:从直接弹窗到各种绕过手法

3.1 无过滤阶段的送分题与验证习惯

web316、web317这几题,几乎就是把XSS的定义写在脸上。输入点是一个叫message或name的GET参数,输出点在<h2>标签里,服务端没有任何过滤。我第一次刷的时候直接就是<script>alert(1)</script>,一次通过。但说实话,这种题真正的考点不是你能不能弹出alert,而是你有没有养成“用唯一标记确认输出位置”的习惯。因为后面的题如果还沿用“随手写script”的思维,大概率会卡住。

我的建议是至少在刷前三题时,把qwer1234这种无害字符串作为默认探测载荷,确认输出位置后再换攻击payload。这个习惯能帮你省掉后面大量试错时间。另外,别用alert(0),很多题目平台对弹窗的内容没有要求,但是万一有校验脚本判断你弹的是不是alert(1),数值不一致就会被判失败,老老实实弹alert(1)最稳。

3.2 script关键字被过滤的四种绕过手法

从web322左右开始,题目开始加过滤了。我遇到的第一种过滤是把script字母替换为空字符串。这种过滤有个比较经典的破法:双写绕过,构造<scr<script>ipt>,因为服务端把中间的“script”删掉后,剩下的正好拼出<script>。类似的还有大小写混淆<ScRiPt>alert(1)</ScRiPt>,但这个只对大小写敏感的服务端过滤有效。

我在刷题时总结了一套快速试错顺序,非常管用:

  1. 原样<script>,测基础。
  2. 交替大小写<ScRiPt>,测过滤是否区分大小写。
  3. 双写<scrscriptipt>,测过滤是否只删一次。
  4. 换标签<img src=x onerror=alert(1)>,测标签白名单。

按这个顺序测,一道题基本一分钟就能定位到过滤规则。千万别在一个payload上死磕,XSS过滤的对抗本来就是“换着角度试探”的过程。

3.3 事件属性与HTML标签清单

过滤掉script标签之后,题目往往会逼你把目标转向“标签+事件”的组合。这套题里出现过的主流标签包括:img配合onerror、svg配合onload、a配合onclick、details配合ontoggle。事件属性里,onerror是最稳定好用的,因为只要标签内的资源加载失败就会立即触发,不需要用户交互。onload在svg和body里也很好使,页面加载即执行。而onclick就吃亏在必须人点一下,在CTF在线验证场景里不太占优势,但偶尔会遇到某个题专门把人引到这条路上。

关于img标签有个细节:<img src=x onerror=alert(1)>里的src=x指向的是一个不存在的资源,所以才会触发onerror。如果你写src=1、src=a,效果一样,因为那个地址永远加载失败;但如果你写src="",某些浏览器的行为会有差异,不一定会触发。所以正经构造时保持src=x这种固定写法最保险。

另外,HTML里所有事件属性都可以被实体编码绕过,比如onerror写成o&#110;error。这种编码思路,我在web330附近的题里用过,作用是帮你绕开只针对明文字符串的事件过滤。但有个前提:浏览器只有在解析HTML标签属性时才会解码HTML实体,如果你把<script>标签本身拆开做实体编码,很多时候反而会被浏览器当成纯文本,导致payload失效。

3.4 标签闭合与拼接思路

到web327前后,题目的呈现方式开始变为“前后代码拼接”。比如输入被放在一段已有HTML的中间:

<div class="container"> <p>用户输入: [这里]</p> </div>

这种情况下,你不仅要考虑payload自身,还得把前端的引号、尖括号关系处理好。最常见的是先闭合前面的内容,再引入自己的标签。

假设输出点在<p>标签内部,而且前面的代码是<p>用户输入:,那payload闭合思路就是先写</p>把前面的段落关掉,再接<script>,最后如果不放心,可以加一段<p>把后面的标签接回来。不过CTF里一般不需要那么讲究,只要弹窗了就行。

在构造闭合payload时,我强烈建议打开浏览器的开发者工具,看Elements面板里当前节点的颜色高亮。

如果闭合的尖括号和引号导致页面结构错乱,高亮颜色会非常直观地暴露问题。我刷题时几乎不用“试了没反应再改”这种蛮干方式,而是直接在DevTools里盯着DOM结构改。

3.5 长度限制和短payload技巧

web331之后有几道题把输入长度限制得很死,比如20个字符、25个字符。这样一来,太长的事件型payload根本放不进去,你必须另寻出路。

几个常用的短payload方案:

  • <svg/onload=alert(1)>,去掉空格,用斜杠代替。
  • <img src=x onerror=alert(1)>,但超过长度就不好使了。
  • <script>alert(1)</script>,虽然长,但可以通过外部引用缩短,比如<script src=//某域名></script>,不过CTF靶场环境里通常没有自己的JS托管服务器,这套思路成功率不高。
  • 如果闭合上下文是属性值里,"onmouseover="alert(1)这种写法能把payload压缩到很短,因为它借用了前面没闭合的属性。

我在碰到长度限制时还有一个取巧的办法:先看输出点是不是可以把注释符//用上,把后面原本存在的HTML代码吞掉,这样就不用在payload末尾补闭合标签了。很多题目在设计时并没想到这个“注释吞尾”的技巧,能用就说明出题人留了空间。

3.6 DOM型题目的前端代码审计实录

web331开始出现真·DOM型XSS。有一道题给我的印象很深,页面主体是一张图片,点击图片会带着hash跳转,大概效果是location.hash被赋值给document.getElementById('a').innerHTML。但如果只是简单地构造#<img src=x onerror=alert(1)>,页面会先解析URL,把#后面的内容当hash,最后innerHTML直接插进去,倒是能弹窗。

这种题最忌讳的事情就是不看JS直接怼payload。我的习惯是优先在Sources面板里把所有JS文件打开,搜索几个关键函数:document.write、innerHTML、outerHTML、insertAdjacentHTML、eval、setTimeout、location。

搜到之后,顺着数据流从源头看到出口,确认“哪个变量可控、经过什么编码、最终塞到哪个DOM API里”。这一步做完,payload基本是水到渠成的事,不用硬试。

4. 刷题过程中的常见问题与排查技巧

4.1 弹窗函数被过滤,怎么办

有少数题做了黑名单过滤,会把alert也替换掉。遇到这种情况,第一反应别慌,换函数。confirm、prompt同样可以弹窗,而且从XSS证明漏洞的角度来说,任何一个能执行的JS函数都可以。很多平台上alert可能单独被过滤,但confirm(1)和prompt(1)没被过滤,直接用就好。

如果alert、confirm、prompt都被过滤了,那就需要更底层的手段:用top或self来引用窗口对象,或者直接利用Function构造函数。比如<svg/onload=top[/alert/](1)>这种写法,利用的是window.alert可以通过window['alert']访问的特性,而方括号里的字符串可以写成别的形式来绕过。不过这种技巧对入门阶段来说没那么刚需,了解即可。

4.2 浏览器本身拦了你的payload

这里是我踩过最大的一个坑:有时候不是题目过滤了你的payload,而是浏览器自带的XSS过滤器把请求拦截了。尤其是Chrome和Edge的反射型XSS保护,当你提交的payload与响应页面里的内容高度相似时,浏览器会直接阻止脚本执行,然后在控制台报错。这时候你看到的不是“payload失效”,而是整个页面被浏览器自己干掉了。

遇到这种情况,我一般优先换Firefox浏览器。火狐对反射型XSS的过滤策略相对宽松,很多在Chrome下没法复现的DOM型XSS也能正常执行。另外,在Chrome下可以通过给payload加一些干扰字符来降低与响应内容的相似度,但这个操作比较玄学,不如换浏览器简单粗暴。CTF本来就是本地环境调试,换浏览器不丢人,结果正确才是第一优先级。

4.3 怎么用开发者工具定位闭合问题

如果你提交的payload没触发,不要反复刷新碰运气,先按F12打开Elements面板,看你的字符串高亮在哪个节点下面。这一步能直接回答三个核心问题:输入有没有被HTML实体转义?输入是被插在属性值里还是标签文本里?前后有没有未闭合的引号或标签?

我习惯的操作是:先在输入框填标记字符,比如zzz"<>',然后在高亮区域看这三个字符的颜色和位置。如果"使字符串变了颜色,说明它在属性值内部;如果<>让节点分裂,说明它被当作HTML标签解析了;如果"和<显示为普通文字,大概率服务端做了转义,这时候这个点就不是XSS注入点了,得换思路。

4.4 通过信息收集提升刷题效率

XSS题目的信息收集比很多人想象中重要。拿到一个页面,我第一件事不是找输入框,而是看URL参数、看表单字段、看页面源码里的>

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

Spring Boot单元测试提速:JUnitGenerator V2.0模板配置实战

1. 为什么我最后选了JUnitGenerator V2.0这个插件1.1 与其手写样板代码&#xff0c;不如让插件先搭骨架做后端开发的人都有过这种时刻&#xff1a;Service层一堆方法&#xff0c;Controller一堆接口&#xff0c;好不容易把业务代码写完&#xff0c;需求方马上要上线&#xff0c…

作者头像 李华
网站建设 2026/10/22 6:00:51

OpenVINO 2026.0正式发布:模型扩展、GenAI强化与智能压缩

OpenVINO™ 2026.0 正式发布了。这个版本从标题就能读出三个信号&#xff1a;模型支持面更广、GenAI 能力更强、压缩工具更智能。如果你正在做本地推理部署&#xff0c;或者手里有部署 LLM、视觉模型、多模态模型的需求&#xff0c;这个版本值得花时间仔细看一遍。我第一时间把…

作者头像 李华
网站建设 2026/10/22 5:59:47

RuoYi-Vue-Plus 集成 AgentScope Harness:纯 Java 实现 AI 编排引擎

1. 为什么要在 RuoYi-Vue-Plus 里塞进一个 AI 编排引擎RuoYi-Vue-Plus 这套脚手架在国内中小型管理系统开发里出镜率极高&#xff0c;权限、数据权限、多租户、代码生成、工作流这些企业级能力基本开箱即用&#xff0c;很多团队拿它当"后台底座"来搭业务系统。但这两…

作者头像 李华
网站建设 2026/10/22 5:58:41

MySQL误删数据怎么办?用binlog实现精准恢复

先说一个我实际碰到过的场景。某天凌晨&#xff0c;线上订单表的数据量突然骤降&#xff0c;监控告警弹出&#xff0c;业务方反馈有人执行了一条范围过大的DELETE。当时我的第一反应不是追问谁执行的&#xff0c;而是立刻问&#xff1a;binlog开没开&#xff1f;格式是不是ROW&…

作者头像 李华
网站建设 2026/10/22 5:58:40

MySQL数据导入导出提速指南:LOAD DATA INFILE与mysqldump实践

1. 数据搬运这件事&#xff0c;为什么总是卡在"导入导出"这一步做数据库的人&#xff0c;早晚都会遇到这一关&#xff1a;要把一百多万行数据从一套环境搬到另一套环境&#xff0c;或者把某张大表导出来给数据分析团队。我见过太多人第一反应是打开图形化客户端&…

作者头像 李华
网站建设 2026/10/22 5:58:31

声明式Agent框架实战:一句话造Agent,告别手搓脚手架

1. 从“手搓 Agent”到“一句话造 Agent”的转变逻辑1.1 为什么“手搓 Agent”正在成为过去式如果你最近半年在折腾大模型应用&#xff0c;大概率经历过这样一个阶段&#xff1a;想做一个能自动查资料、写代码、跑流程的智能体&#xff0c;结果光是搭骨架就耗掉两三天。工具调用…

作者头像 李华