CtfShow Web入门40-50题:从信息泄露到多漏洞组合的进阶实战

📅 发布时间:2026/10/16 9:45:44
CtfShow Web入门40-50题:从信息泄露到多漏洞组合的进阶实战
做了这么多年 Web 安全相关的练习和带新人我发现一个特别有意思的现象很多入门的选手在 CtfShow 的 Web 入门系列里前面 30 题做得飞快一路扫过去全是信息泄露、简单绕过感觉像在玩解谜游戏。但到了 40 到 50 这个区间画风突然就变了——题目开始把多个知识点串起来PHP 版本特性、伪协议、上传绕过、命令执行过滤全都揉在一起考很多人第一次在这里卡上两三天。这篇文章就是想把 CtfShow Web 入门 40-50 这十题背后的东西讲透彻适合刚刷完基础题、准备进阶的选手也适合那些做完了但没搞懂为什么的人回头查漏补缺。这个阶段最核心的价值不是让你多拿几个 flag而是逼着你从“会用工具”变成“懂原理”。因为到了 40 题往后几乎所有题的 payload 都不是固定的你必须知道服务端到底在做什么才能构造出能过的那一条。我自己刷完这一段的感受是它其实是一道分水岭跨过去了后面看代码审计、PHP 特性题就会顺畅很多。1. 40-50 阶段到底在考什么入门到进阶的分水岭1.1 题型分布与难度变化CtfShow 的 Web 入门系列前 40 题基本覆盖了信息泄露、前端绕过、简单的文件包含和常见的 SQL 注入整体风格是“单点突破”。但从 40 题开始明显能感觉到题目设计思路变了一个题目里往往藏着两三个考点而且其中一个考点会被刻意隐藏需要你先找到入口才能触发真正的漏洞点。这个阶段常见的出题方向包括带过滤的 SQL 注入、日志或会话文件包含、上传图片马配合解析漏洞、命令执行的空格与关键字绕过、PHP 伪协议读取源码、以及一部分需要留意环境版本差异的特性题。它们不是每道题只考一个点而是经常把文件包含当入口把源码泄露当跳板最后在日志注入或上传点收尾。我见过太多人在这里栽跟头的原因是同一个习惯性地用扫描器或者一句万能 payload 去打全部题目。比如看到id1就直接上 sqlmap看到上传点就直接传一句话木马结果在 40-50 这个区间几乎全军覆没。原因很简单这些题目里大多数都有参数过滤或前置校验工具默认的 payload 往往第一层就被拦掉了。1.2 为什么“知道答案”不如“知道过程”这个阶段有个很典型的误区很多人做完题之后只记了一个最终 payload比如?filephp://filter/convert.base64-encode/resourceflag.php然后遇到类似的题照样套套不出来就以为题目变了。其实问题不在于 payload 本身而在于你知不知道为什么要用php://filter而不是直接include以及为什么还要 base64 编码一层。CtfShow 40-50 这类题目最锻炼人的地方就是强迫你去读源码、去猜服务端逻辑、去试错。比如文件包含题你直接包含 flag.php 的时候页面什么都没显示这时候第一反应应该是读取源码而不是继续盲目包含。用伪协议把文件内容读出来看到源码里的过滤规则才知道下一步该绕过什么。这个“代码信息收集 → 逻辑推断 → 构造 payload”的循环就是这一阶段真正的训练目标。我自己带过几个人他们的共同进步节点都在这个区间之前做题是“撞答案”到这里开始被迫“读代码”等这十题做完基本都能自己分析一个陌生的 PHP 程序了。这个能力比任何单个题目的 flag 都值钱。1.3 环境差异是最大的隐形考点还有一点必须提醒CtfShow 平台的题目环境大多是固定容器但不同年份、不同平台的题目容器 PHP 版本可能不一样。最典型的就是 PHP 5 和 PHP 7 对某些函数的处理差异。比如intval在 PHP 5 下对超长数字的处理、preg_match对数组参数直接返回 false、strpos遇数组报错等这些在 40-50 区间都出现过。这也意味着你不能只记住“某道题我这么绕过去了”而是要连版本特性一起记。平时做题的时候我强烈建议养成一个习惯每道题做完了顺手在笔记里标注一下这个 payload 依赖的 PHP 版本特性或者是在哪个版本的容器里测试通过的。后面等你回头复习时这个信息比“当时怎么拿的 flag”有用得多。2. 工具与语言基础提前把该准备的都准备好2.1 Burp Suite 在绕过场景中的核心操作到了 40-50 这个阶段Burp Suite 基本是标配了。不是说别的工具不行而是它的抓包改包流程最直观前端校验可以用浏览器直接绕过但后端过滤你必须亲眼看到服务端返回的原始响应才能判断。我推荐按这个思路用 Burp先关掉拦截把题目页面正常走一遍流程用 History 功能看完整的请求列表找到关键请求后右键 Send to Repeater在 Repeater 里改参数、改请求头、改方法如果题目里做了请求频率限制或者需要走完前置步骤再配合 Match and Replace 自动改包实际做题的时候最常用的几个操作是把GET改成POST、在 Cookie 或X-Forwarded-For头里加内容、把 Content-Type 改掉。很多人卡在文件上传题就是因为只会在前端改后缀没想过 Burp 里要把整个 Content-Type 一起改掉。服务端校验 MIME 的时候只看这个头不看你文件名是什么。2.2 写一手快速请求脚本的收益Burp 能解决大部分交互式调试但遇到需要大量试错的情况比如遍历几个候选 payload、批量测试不同的编码方式手写 Python requests 脚本会高效很多。我不建议新手一上来就写框架只需要会用最基础的 requests 就够了import requests url http://target/flag.php payload {id: 1 and 11-- -} resp requests.post(url, datapayload) print(resp.text)实测下来这个阶段最需要的不是复杂脚本而是快速修改字典和自动打印响应内容的循环。你可以自己维护一个常见 payload 列表把需要测试的变量值全部放进去跑一遍看哪个响应长度与众不同再回 Burp 里细看。这里有一条很重要的经验写脚本的时候一定要把响应状态码、响应长度、响应内容的关键片段都打印出来。很多 filter 类题目返回的是 base64 字符串或者返回的是空页面但 Set-Cookie 里塞了信息只打印一个 text 很容易漏掉关键内容。2.3 本地容器与在线靶场的差异注意点做题做多了你一定会遇到一种情况本地复现同样的代码payload 能用但在 CtfShow 环境里就是打不通或者反过来。这种差异通常来自这么几个地方PHP 版本不一致这是最大的坑题目容器里禁用了某些函数比如system、exec但本地没禁用题目环境变量、flag 文件名与本地不一样比如本地是flag.php题里可能是flag_123.php所以我的习惯是凡是涉及函数可用性、php 伪协议可用性的题目先在本地起一个相同版本的 PHP 容器来验证。实际项目中如果怀疑是版本问题最快的方式是直接在题目里尝试输出phpversion()如果被过滤了就尝试通过报错信息或者 phpinfo() 页面间接判断。环境差异这个坑90% 的情况下是版本问题。3. 高频漏洞类型的核心细节原理比 payload 更重要3.1 SQL 注入的过滤绕过思路40-50 里出现的 SQL 注入题基本不会让你直接 or 11-- -就拿 flag。常见的过滤包括关键字过滤select、union被替换为空、空格过滤用/**/或%0a代替、注释符过滤--被拦需要换#或者;。先想清楚为什么这些绕过方式有效你用的时候才不心虚把关键字替换成空如果只替换一次就可以用selselectect这种双写绕过因为过滤完剩下的还是select空格被过滤本质是因为服务端可能会用preg_match检查空白字符而/**/在 MySQL 里就是合法的分隔符它起到和空格一样的作用注释符被过滤有时候可以改用闭合单引号的方式不依赖注释符我自己的做题顺序是先测闭合符再测过滤规则最后才是构造 payload。测闭合符的时候可以输入1、1、1)、1)-- -等几种根据报错或者回显差异判断。拿到过滤规则后用一个最小化 payload 去逐字验证比如先测select被没被过滤再测union这样比一次扔一个完整 payload 去撞效率高得多。这个阶段还要注意一点union 注入对列数有要求通常需要先order by猜列数如果order by也被过滤了可以试试group by或者用union select 1,2,3...逐个尝试。不要怕请求多这种题本来就是一个试错的过程。3.2 文件包含与伪协议的组合利用文件包含题在 40-50 里出现频率特别高而且往往不是单独出现。核心考点其实就两个include了哪个文件、你能否控制这个文件名。如果参数直接可控比如?pagexxx那第一件事永远是读取源码。用php://filter是常规操作但很多人不知道为什么要转 base64php://filter/readconvert.base64-encode/resourcexxx.php因为直接include一个.php文件时文件内容会被当成 PHP 代码执行你看到的是执行结果而不是源码。加一层convert.base64-encode输出的就是 base64 编码后的源码文本不会被当成代码执行所以源码会直接显示在页面上。你再去解码就看到了服务端逻辑。另一个常考的姿势是包含日志或会话文件。比如 Web 服务访问日志里会记录 User-Agent你把恶意 PHP 代码写进 User-Agent然后再通过文件包含去包含日志文件代码就会执行。这种利用方式的重点在于你得先知道日志文件的绝对路径而这通常需要配合 phpinfo() 页面或者之前的源码泄露来判断。这里可能有人会问那直接包含图片马可不可以也可以但那是文件上传题的路线跟纯文件包含题不一样。文件包含题的重心是找可写的、内容可控的文件日志、session 文件、临时文件都是目标。3.3 文件上传的常见限制与绕过路径文件上传题到了这个阶段基本不会只靠改后缀过。常见的校验方式有三种前端 JS 校验、服务端 MIME 校验、服务端文件头校验。前端校验可以直接在 Burp 里改包绕过MIME 校验改 Content-Type 就行文件头校验则需要你在文件内容里补上GIF89a或者%PDF之类的头。但真正值得花时间想的是解析漏洞。很多人在上传题里拿了一句话木马却连不上是因为文件根本没被解析成 PHP或者文件名被重命名了。比如上传shell.php.jpg服务端如果只是保存文件名没做重命名而容器又有解析漏洞那这个文件可能就会被按 PHP 解析。反过来如果服务端把所有上传文件都重命名成了随机名或加了时间戳那就算上传成功你也找不到文件路径题目这时候大概率会通过源码泄露告诉你上传目录和文件名规则。实战里最稳的流程是这样先把一个正常的图片传上去看返回的路径和文件名格式再传一个改了后缀的图片看服务端报什么错根据报错类型判断过滤规则再针对性构造 payload。不要一上来就传木马因为如果你的恶意文件被存下来了但没有解析反而会污染环境导致后面没法正常测试。还有一个很容易忽略的点很多上传题的上传目录和执行目录是分开的。就算你把 PHP 马传上去了访问这个路径只是下载而不是执行。这时候需要找是否有include点可以配合包含你上传的文件或者能不能通过phar://协议去触发反序列化。这些都属于进阶玩法了但在 40-50 里确实出现过类似的组合。3.4 命令执行的空格与关键字过滤命令执行题只要出了基本都是过滤了空格的。因为最简单的命令拼接就是|cat flag.php但如果你输入|cat flag.php发现报错很可能就是空格被过滤了。空格可以用${IFS}代替也可以写成$IFS$9因为$9是空变量$IFS$9拼在一起就等于一个空格。关键字过滤也常见比如cat被拦。可以用tac代替因为tac是反向输出读文件效果一样。也可以用cat、c\at这种引号或反斜杠绕过因为 shell 解析的时候引号和反斜杠会被去掉最终执行的还是cat。如果过滤的是flag这个字符串可以试试fla$*g或者flag这类变量替换写法。这里给一个实用建议命令执行题不要只盯着cat和ls多试试nl、more、less、head、tail。有些题卡你cat但不卡nl而且nl flag.php输出的行号反而更方便看代码结构。4. 一道典型综合题的手工拆解全过程4.1 信息收集阶段这个阶段我习惯先把整个页面和功能点摸一遍而不是急着传参数。比如打开题目页面可能就是一个登陆框或者是一个带几个链接的导航页那就先把所有能点的链接都点一遍看 URL 参数的变化。如果是带参数的文件包含题URL 里通常会有?pagehome这种结构。这时候先不要把这个参数改成flag.php而是先试?pageindex.php看会不会有循环包含或者报错。报错信息往往能泄露绝对路径这个路径在后面的日志包含、session 包含里非常有用。像这种综合题我强烈建议先做源码收集。常见的入口包括首页源码注释里的提示、robots.txt、www.zip或backup.sql这类备份文件、.git目录泄露。CtfShow 入门系列里就出现过直接在注释里藏提示的题目很多人扫了半天没想到先看页面源码。4.2 确认漏洞点并构造 payload拿到源码之后不要急着找 flag先确认漏洞点。比如源码里看到include $_GET[page]那漏洞点就是文件包含看到system($_GET[cmd])那漏洞点就是命令执行看到preg_match对参数做过滤那就把过滤规则摘出来一条一条分析。我在这个阶段常用的手法是构造一个“探针 payload”目的是确认过滤规则而不是直接拿结果。比如在命令执行题里先执行echo 123看 123 有没有被输出再执行ls看能不能看到文件列表如果被过滤了就换${IFS}再试一次。每一次改动只调整一个变量这样哪个字符触发过滤一目了然。这时候用 Burp Repeater 或者一个简单的 Python 循环脚本都行。我的习惯是用脚本把不同的 payload 放在列表里跑完对比响应长度。响应长度明显不一样的那一条就是成功触发了漏洞的再拿回 Burp 里做精细调试。这比肉眼一条条看好用得多。4.3 从回显到拿 flag 的细节处理拿到 flag 的最后一公里往往最容易出问题。比如文件包含题你用php://filter读到了源码但 flag 不在这个文件里或者命令执行题里cat flag.php输出了一大段 HTML 代码flag 夹杂在注释里没注意到。我常用的一个手段是把输出内容的前 50 行和最后 50 行都看一遍不要只看前面。很多 flag 藏在文件的末尾注释里或者放在一个完全不显眼的位置。另一个手段是优先读取index.php、flag.php、config.php这类名字最有可能的文件读完之后如果还没有再考虑读目录列表。如果题目是命令执行且ls能看到文件列表直接按名字cat就好。如果看不到列表可以试试nl flag.php输出行号版内容这样不会被 HTML 渲染后的空白干扰。遇到 flag 不显示的优先想到“是不是被当成 HTML 标签执行了”这种情况在纯文本接口里很常见用view-source:或者直接在响应原文里搜ctf、flag关键词就找到了。5. 做题实战中的典型问题与排查方法5.1 相同的 payload 在别处能用这里为什么不行这个问题出现频率极高根源一般有三个。第一个是过滤规则不同比如上一道题过滤的是select这一道题过滤的是union select你直接复制上一题的 payload 当然过不了。第二个是环境版本差异PHP 5 和 PHP 7、MySQL 5 和 MySQL 8 在注释、报错信息上都有区别。第三个是入口条件不同比如上一题直接传参就行这一题可能要先登录或者要先满足某个前置条件。排查的方法是一点一点拆解 payload用最小化请求去测试每一段。比如你先只测1看报不报错再测1 and 11-- -看回显差异这样就能定位到是哪一层被过滤了。直接换一个完整 payload 去撞是效率最低的做法。5.2 源码读了但没读到 flag下一步应该做什么这种情况通常不是你姿势错了而是目标文件找错了。我一般的扩展思路是先读当前文件的源码看里面有没有include或require其他文件顺着代码里的引用链继续读。比如读了index.php发现它include common.php那common.php也必须读一遍flag 可能就藏在被包含的那个文件里。如果源码里没有明确引用就可以尝试读配置类文件比如config.php、db.php、setting.php。这些文件往往会定义数据库连接信息或者自定义函数虽然不直接叫 flag但里面可能包含自定义的加密逻辑flag 要经过这个逻辑才能看到。再不行就扩大读取范围用目录扫描工具看看有没有隐藏文件或者直接读.htaccess、php.ini这类配置文件。5.3 上传了文件但访问不到或不被解析上传成功但访问不到大多数情况是文件名被重命名了。服务端可能在保存时加了时间戳、随机字符串或者改了扩展名。这时候你可以先传一个正常图片从返回包里看它保存成了什么名字找规律。有些平台会返回上传成功的完整 URL那就直接看 URL 里的文件名结构。如果不被解析那问题多半出在扩展名上。服务端可能在后端重写了扩展名比如把你传的所有文件都保存成了.jpg这时候单独的.jpg是不会被 PHP 解析的。你可以试试上传一个.php.jpg或者.phtml有些容器支持多后缀解析。也可以配合.user.ini或者.htaccess来做但这属于进阶操作建议先把基础上传流程摸清楚再碰这些。5.4 flag 在响应里看不到完整内容命令行执行题里最常见的是 flag 内容太长被终端截断或者输出在 HTML 里被浏览器隐藏了。这种情况先看原始响应不要看渲染后的页面。在 Burp 里看 Response 原文或者用 Python 脚本直接打印响应内容很多“找不到 flag”的问题就解决了。如果原始响应里真的没有那可能是输出被注释掉了。比如!-- flag{...} --你在浏览器里看不到但在源码里能看到。如果源码里也没有可能是输出被重定向到了文件比如题目把 flag 写到了某个临时文件里。这时候要么想办法读取那个文件要么通过报错信息把内容带出来比如故意构造一个包含 flag 数据的报错让服务端把 flag 拼进错误信息里返回。6. 从做题到防御漏洞背后的修复视角6.1 SQL 注入的修复思路把做题时的 payload 反过来理解你就能知道防御该怎么做了。为什么 or 11-- -能绕过因为查询语句是拼接出来的。防御的核心就是参数化查询不管是 PDO 还是 mysqli 预处理只要参数不再直接拼进 SQL 语句注入就是用不了的。注意字符串过滤不等于安全。像addslashes、preg_match这类黑名单方案总会有漏网之鱼。做题的人天天在找过滤绕过做防御的人就应该默认过滤是能被绕过的直接上参数化和白名单校验才是正途。实测下来一个简单的 PDO 预处理就能拦下绝大多数注入 payload比自己写十层过滤靠谱得多。6.2 文件包含的修复思路文件包含漏洞的本质是变量文件路径来自用户输入而且没有做白名单限制。最直接的修复方式是设置一个允许包含的文件列表任何不在列表里的路径一律拒绝。比如页面本来就只有home和about两个页面那就只允许pagehome和pageabout其他值全部 404。如果业务逻辑上必须让用户传文件名那就做好路径校验禁止../、禁止绝对路径、禁止伪协议。同时把allow_url_include关掉这样远程文件包含和伪协议直接失效。很多人在线上环境开着allow_url_include就是为了省事这是文件包含漏洞最大的放大器。6.3 命令执行与上传的修复思路命令执行的修复核心就一句话不要用system、exec、shell_exec去执行用户输入。如果业务确实需要执行命令那就用escapeshellarg和escapeshellcmd做好转义再加上严格的白名单。PHP 应用还可以直接在php.ini里把disable_functions配上把用不到的危险函数全禁掉。文件上传的防御重点有三个文件内容校验、文件名重命名、上传目录禁止执行脚本。文件内容校验不只是看 MIME 和扩展名要真正检查文件头比如 PNG 的89 50 4E 47、JPEG 的FF D8 FF。文件名重命名是防止解析漏洞的关键传上去的文件一律改成随机名加白名单扩展名比如.dat或者.jpg不要保留用户原始文件名。上传目录配置成不执行任何脚本即使马传上去了也跑不起来。我做安全这行越久越觉得CtF 里那些刁钻的 payload表面是在秀技巧本质都是在提醒防御方用户的输入永远是不可信的所有的过滤都只是延迟而不是阻止。把这句话刻在脑子里无论做题还是做防御思路都会清晰很多。7. 关于这个阶段做题的一些个人建议刷完 40-50 这十题我最想提醒后来者的一点是不要追求“快速拿 flag”要追求“被卡住之后怎么自己走出来”。第一次做不出来太正常了我见过大部分人在第 41、42 题上卡一天以上这不是能力问题是还没适应这个阶段“必须读代码才能做出来”的节奏。每道题做完之后花二十分钟做一个整理是值得的。我的习惯是每道题记录三件事触发漏洞的入口参数是什么、服务端用什么方式做了过滤、我用什么方式绕过了过滤。这三行笔记写完一道题才真正算吃透了。后面再遇到类似的题翻笔记比重新试错快得多。另外如果是第一次接触伪协议、命令执行绕过这类知识点本地搭一个 PHP 容器配合 DVWA 或靶场环境做实验会比直接在线上题里试错轻松很多。线上题目环境被你搞乱了之后很难恢复本地环境随便折腾。理解原理再去打题你会发现那些看起来高深的 payload 都特别直白无非就是知道了服务端在做什么然后找一个它没拦住的角度而已。