周末闲着翻靶场,看到GKCTF2021的babycat这道题,名字很萌,点进去却是标准的命令注入。这题卡了我大概四十分钟,最后解出来才发现核心就是一句shell_exec把用户输入直接拼进了cat命令。这篇文章不打算按时间线流水账,而是把解题的完整思路和绕过过滤的过程拆开讲,附带我踩过的坑。适合刚开始接触CTF Web方向、想搞懂命令注入到底怎么找怎么打的同学,也适合比赛后想系统复盘的老手。1. 初见babycat:先别急着扫目录,先想想出题人想干什么1.1 页面长什么样打开题目,页面非常简洁:底色是浅黄色,中间放了一张慵懒的橘猫动图,下方是一个输入框和一个“查询”按钮。输入框前面的提示文字是Cat Name,按钮上的文字是Query。提交任意内容,比如abc,URL会变成/?name=abc,页面上多出一行文字:Cat abc not found.。再试试mimi、tom之类有名字感的词,仍然是not found。这里很容易让人以为是一个查询猫咪数据库的功能。浏览器里看不到任何按钮有特殊操作,表单是GET方式。我顺手按了F12看源码,发现页面底部有一段HTML注释:<!-- maybe you can try /flag -->。这个注释在真实比赛中基本就等于明示flag位置,也是出题人给新手的一条引导线。到这里,大概能判断这个题八成是让我读服务器上的某个文件,或者通过某种方式执行命令。1.2 名字是最大的提示题目叫babycat,相比很多CTF题目的花哨命名,这名字信息量很大。cat在Linux里是最常见的查看文件命令,而“baby”一般表示基础、入门。所以出题人想考的十有八九是命令注入——把用户输入拼到cat命令里执行。如果对这个提示不敏感,很容易在扫描目录上浪费时间,纠结是不是上传题、反序列化题。我后来复盘发现,这道题的正确打开方式就是先看名字,再想功能,最后才是工具扫描。很多WriteUp会把重点放在payload上,但我觉得更关键的是这个“读题”的过程:题目给了猫图、给了Cat Name、给了注释/flag,三个线索拼起来,指向的就是一个可以被控制的文件路径参数,最终通过cat命令把flag文件的内容输出到页面。如果一开始就往其他方向想,后续所有动作都会跑偏。1.3 信息收集不是无脑扫,要有目标我见过不少新手在拿到题目后第一件事就是上dirsearch扫目录,这没有错,但应该带着问题去扫。在我做这题时,常规信息收集做了三件事:看响应头:确认是Nginx还是Apache,有没有版本信息泄露,有无X-Powered-By: PHP/7.x。这题直接暴露了PHP,说明大概率是PHP相关漏洞。找入口文件:尝试index.php、robots.txt、sitemap.xml、www.zip、.git等常见文件。这里没有直接出结果,但至少排除了几个方向。看请求参数:页面交互只有name一个参数,所以重点就放在这个参数上。这些动作不是漫无目的,而是为了回答一个问题:题目到底有哪些可控输入。目录扫描本身不是目的,目的是尽可能找出攻击面。babycat这种题攻击面非常小,唯一的入口就是那个name参数,所以后面所有精力都应该集中在参数测试上。2. 第一波试探:输入框背后的参数拼了什么命令2.1 基线请求与URL参数识别先建立基线请求,用浏览器地址栏或者抓包工具发送一个正常的请求:GET /?name=abc HTTP/1.1 Host: 目标靶机返回内容是Cat abc not found.。这个abc原样出现在响应里,说明服务端确实在处理这个参数,并且很可能是把参数值直接拼到了某段输出逻辑中。我尝试把abc改成abc def,URL编码后是abc%20def,响应里的Cat后面出现空格,变成了Cat abc def not found.。这说明参数值会被原样拼接到字符串里。此时有两种典型可能:第一种是PHP代码类似echo "Cat $name not found.";,仅仅是字符串拼接,没有命令执行;第二种是后端把$name拼到了shell命令里,比如cat $name,执行失败后的错误信息被捕获并输出。区分这两种情况,最直接的办法是输入带有shell语义的字符,比如;、|、&、$等。2.2 从报错确认shell_exec我输入?name=;ls,页面返回了两行内容,一行是ls: cannot access '': No such file or directory,另一行是一串文件名,包括index.php、flag、styles.css。这里其实有两个意外收获:第一,命令注入存在;第二,当前目录下居然有一个叫flag的文件,甚至不需要去读根目录下的/flag。再输入?name=;cat flag,我以为会直接拿到flag,没想到页面返回sh: 1: cat: not found。这就很奇怪了,明明ls能执行,cat却不存在。仔细看一下报错,sh: 1: cat: not found说明环境变量有问题,或者cat命令被重命名、移除了。为了确认,我输入?name=;which cat和?name=;which ls,发现ls在/bin/ls,而which cat没有任何输出,连错误提示都没有。这基本确定是容器环境里没有cat,或者cat被出题人用某种方式隐藏了。后来我才意识到,这道题叫“babycat”,但环境里没有cat命令,出题人是在逼选手换命令。常见替代命令有tac(倒序输出,但读普通文件足够)、more、less、head、tail、nl、awk等。这个知识点如果平时没积累,临场很容易卡住。2.3 源码泄露把答案送到眼前顺着命令执行的思路,我尝试读取源码:?name=;ls -la,输出里有一个index.php,权限是-rw-r--r--,可以直接访问。我试着用命令把它打印出来:?name=;head index.php。head命令是存在的,所以成功输出了源码。源码不长,核心代码如下:<?php $name = $_GET['name']; if (preg_match('/\/|cat|more|less|tail|\s|flag/i', $name)) { die('forbidden'); } $cmd = "cat " . $name . " 2>/dev/null"; $output = shell_exec($cmd); echo $output ? $output : "Cat $name not found."; ?>这段源码信息量很大。参数$name被直接拼进cat命令,用的是shell_exec执行。过滤规则是preg_match匹配以下模式:斜杠/、字符串cat、more、less、tail、空白字符\s、字符串flag。也就是说,斜杠、空格、常见的读文件命令以及flag关键字都被过滤了。我前面输入;ls之所以成功,是因为没有触发这些规则;输入;cat flag失败,一方面因为没有cat命令,另一方面也触发了flag关键字过滤,即使有cat也会被拦截。从源码还能看出,2>/dev/null被拼在命令末尾,所以命令执行时的报错信息会被丢弃,页面只展示命令的标准输出。这解释了为什么cat不存在时没有直接看到bash: cat: command not found,而sh: 1: cat: not found是来自另一个分支——可能是shell_exec返回时由环境抛出的。不管细节如何,至少现在我们拿到了源码,接下来的利用就有明确方向了。3. 命令注入的三道过滤:空格、敏感词和斜杠3.1 分号注入打通第一层源码里的拼接方式是cat " . $name . " 2>/dev/null,那么当我提交;ls时,完整命令变成:cat ;ls 2>/dev/nullShell会先执行cat,但因为没给参数,cat从标准输入读取,在Web环境下标准输入立刻到达EOF,于是cat干净退出,后面紧跟的ls正常执行。ls的标准输出被返回,所以我能在页面上看到文件列表。这里的关键是:分号没有被过滤,所以整个命令可以被分隔成多条子命令。也许有人会问,为什么不直接用管道|?源码确实没有过滤|,但我测试时发现管道符在反引号场景下容易和后面命令的后缀拼接混淆,而且分号更直观,能明确看到每一步执行结果。在真实题目中,优先尝试分号是完全合理的思路。3.2 把过滤规则测出来拿到源码后,我决定把过滤规则完整测一遍,避免瞎猜。下表是我当时的测试记录:Payload预期实际响应?name=;ls执行ls返回文件列表?name=;cat flag读取flag返回forbidden?name=;tac flag用tac读flag返回forbidden(触发了flag关键字)?name=;tac fla*通配符读flag返回forbidden(存在空格,触发\s过滤)?name=;tac${IFS}fla*无空格+通配符成功输出flag这个表格非常直观:第一层被flag挡住,第二层被空格挡住,第三层被cat这个词本身挡住。我们需要分别绕过三个限制。3.3 组合绕过拿到flag逐个拆解:空格过滤:用${IFS}代替空格。IFS是Shell里的内部字段分隔符,默认包含空格、制表符和换行,${IFS}在命令展开时会变成空格,而且不会触发\s正则匹配。所以tac${IFS}fla*等价于tac fla*。cat被过滤:简单粗暴换命令,tac是cat的反向输出,同样能读文件。正则匹配的是连续的cat字符串,tac里面没有连续出现cat,所以通过。flag关键字被过滤:用通配符fla*代替,Shell在执行时会把它展开成当前目录下所有以fla开头的文件,正好匹配flag。正则匹配的是flag这个完整字符串,fla*里没有,所以也通过。最终payload是:?name=;tac${IFS}fla*完整命令变成:cat ;tac${IFS}fla* 2>/dev/null其中cat空读后退出,tac${IFS}fla*展开为tac fla*,由于没有空格,成功绕过了过滤,页面上直接输出flag内容。这道题到这里就结束了。顺便提一句,如果flag不在当前目录而在根目录,还需要处理斜杠过滤。一个常见思路是用环境变量拆出斜杠,比如${HOME:0:1}可能得到/,但需要确认环境变量。另一个更通用的做法是避免输入斜杠,把文件移到当前目录不太现实,所以很多命令注入题把flag放在当前目录是有意降低难度。babycat显然属于这种送分类。4. 如果题目不按套路走:文件包含与伪协议的并行路线4.1 为什么命令注入经常和文件读取混着考打CTF的Web题时经常会发现,命令注入和文件读取在现象上非常接近:都是通过控制一个参数去读文件内容。区别在于底层函数——命令注入走的是system、shell_exec、exec这些Shell执行函数,文件读取走的是file_get_contents、readfile、include这些文件操作函数。题目名字叫babycat,核心线索又是cat命令,所以命令注入是首选方向。但如果某道题隐藏了源码,新手往往很困惑:输入;ls没有任何反应,输入../index.php反而能看到源码。这时候就要立刻切换思路:也许后端根本没进Shell,只是在做文件包含或文件读取。继续把命令注入想下去只会浪费时间。4.2 php://filter 读源码的具体操作假设babycat改成这样:参数$name被拼进include($name . '.html'),那么直接读index.php会触发PHP解析,看不到源码。此时需要用PHP流包装器,把文件内容做base64编码后再输出,常见payload是:?name=php://filter/convert.base64-encode/resource=index.php页面会输出一串base64,解出来就是完整源码。这个技巧和高阶命令注入一样,本质都是绕过“直接输出”的限制。虽然babycat不需要走这条路,但如果你不想只做一个只会复制payload的人,应该理解为什么convert.base64-encode能读到源码:它让包含的文件内容先经过过滤器,再被输出,PHP不会把结果当作代码执行。4.3 两条路线的判别方法实操中的快速判别方法很简单,推荐大家记一下:提交?name=;ls,如果响应里有目录列表或命令报错,说明命令注入成立。提交?name=../../etc/passwd,如果响应里出现root:x:0:0这样的内容,说明文件读取成立。提交?name=php://filter/convert.base64-encode/resource=index.php,如果响应变成一串base64字符,说明存在include/require且可以直接读源码。这三种palyoad覆盖了Web入门阶段的三大核心漏洞:命令注入、目录穿越、文件包含。babycat命中第一种,但很多变体题是把三者混在一起考,所以最好都在脑内形成一个固定判断流程。5. 复盘与延伸:从babycat到命令注入全家桶5.1 实战中的两个思维误区第一个误区:以为所有命令注入都得靠cat读文件。babycat这个环境里没有cat,直接导致我一开始怀疑题目是否根本不是命令注入。后来才意识到,命令注入的第一原则是“环境里有什么命令用什么命令”,而不是“我背过什么payload就用什么payload”。ls、tac、head、nl甚至php都能读文件,遇到cat缺失时,先执行ls /bin看看有哪些可用命令。第二个误区:被过滤逻辑挡住后,只想着大小写绕过,没想到通配符和Shell展开。比如flag被过滤,很多人会尝试FLAG、fLaG,但正则一旦保险起见加了i修饰符,大小写就完全没用。而fla*是Shell展开,在正则过滤的那一刻还只是一个普通字符串,根本不包含flag,只有在Shell执行时才会变成文件名。这个时间差是绕过关键字过滤的经典手法。5.2 命令注入的绕过工具箱我根据平时刷题经验整理了一个小型工具箱,babycat里用了前几项,后面的也建议背下来:过滤点绕过方法示例空格${IFS}、$IFS$9、%09(URL制表符)tac${IFS}flag关键字换命令、通配符、字符串拼接tac fla*、c""at flag斜杠环境变量拆解、$PATH替换${PATH:0:1}flag分号/管道换行符%0a、$()、反引号?cmd=ls%0acat flag命令回显用curl外带、DNS查询、写文件再访问适合无回显场景这里要特别提醒:外带数据在正规授权靶场练习没问题,但绝对不要对着未授权目标做,否则性质完全不同。CTF题目本身就是虚拟靶机,练习时注意边界即可。5.3 新手练习命令注入的正确姿势想真正吃透命令注入,建议自己用Docker搭一个最小靶场。比如在本地写一个cmd.php:<?php $cmd = $_GET['cmd']; system($cmd); ?>然后挂在Nginx或Apache下,反复提交各种payload,观察每个字符是如何影响最终命令的。只有亲手执行过;whoami、|whoami、$(whoami)、反引号构造的差异,才能真正理解Shell的解析顺序。光看WriteUp永远记不牢。刷题平台方面,选那种有官方题解和WriteUp沉淀的社区题,一道题做完后对比别人的思路,比闷头做十道重复题效率高得多。babycat这种送分题,价值恰恰在于让你把命令注入的基础链路完整跑一遍:确认注入、测过滤、组合绕过、拿到flag、复盘发散。说实话,这题迷惑性在于名字和页面的第一印象,但只要抓住“cat”这个双关提示,后面的每一步都有迹可循。我在拿到flag之后又把源码读了一遍,发现这题真正想教给新手的不是某个高深技巧,而是在遇到过滤时保持思路灵活:命令不够换命令,字符被过滤就找等价替代。这个能力在后面的Web题目里会用得越来越频繁。
周末闲着翻靶场,看到GKCTF2021的babycat这道题,名字很萌,点进去却是标准的命令注入。这题卡了我大概四十分钟,最后解出来才发现核心就是一句shell_exec把用户输入直接拼进了cat命令。这篇文章不打算按时间线流水账,而是把解题的完整思路和绕过过滤的过程拆开讲,附带我踩过的坑。适合刚开始接触CTF Web方向、想搞懂命令注入到底怎么找怎么打的同学,也适合比赛后想系统复盘的老手。1. 初见babycat:先别急着扫目录,先想想出题人想干什么1.1 页面长什么样打开题目,页面非常简洁:底色是浅黄色,中间放了一张慵懒的橘猫动图,下方是一个输入框和一个“查询”按钮。输入框前面的提示文字是Cat Name,按钮上的文字是Query。提交任意内容,比如abc,URL会变成/?name=abc,页面上多出一行文字:Cat abc not found.。再试试mimi、tom之类有名字感的词,仍然是not found。这里很容易让人以为是一个查询猫咪数据库的功能。浏览器里看不到任何按钮有特殊操作,表单是GET方式。我顺手按了F12看源码,发现页面底部有一段HTML注释:<!-- maybe you can try /flag -->。这个注释在真实比赛中基本就等于明示flag位置,也是出题人给新手的一条引导线。到这里,大概能判断这个题八成是让我读服务器上的某个文件,或者通过某种方式执行命令。1.2 名字是最大的提示题目叫babycat,相比很多CTF题目的花哨命名,这名字信息量很大。cat在Linux里是最常见的查看文件命令,而“baby”一般表示基础、入门。所以出题人想考的十有八九是命令注入——把用户输入拼到cat命令里执行。如果对这个提示不敏感,很容易在扫描目录上浪费时间,纠结是不是上传题、反序列化题。我后来复盘发现,这道题的正确打开方式就是先看名字,再想功能,最后才是工具扫描。很多WriteUp会把重点放在payload上,但我觉得更关键的是这个“读题”的过程:题目给了猫图、给了Cat Name、给了注释/flag,三个线索拼起来,指向的就是一个可以被控制的文件路径参数,最终通过cat命令把flag文件的内容输出到页面。如果一开始就往其他方向想,后续所有动作都会跑偏。1.3 信息收集不是无脑扫,要有目标我见过不少新手在拿到题目后第一件事就是上dirsearch扫目录,这没有错,但应该带着问题去扫。在我做这题时,常规信息收集做了三件事:看响应头:确认是Nginx还是Apache,有没有版本信息泄露,有无X-Powered-By: PHP/7.x。这题直接暴露了PHP,说明大概率是PHP相关漏洞。找入口文件:尝试index.php、robots.txt、sitemap.xml、www.zip、.git等常见文件。这里没有直接出结果,但至少排除了几个方向。看请求参数:页面交互只有name一个参数,所以重点就放在这个参数上。这些动作不是漫无目的,而是为了回答一个问题:题目到底有哪些可控输入。目录扫描本身不是目的,目的是尽可能找出攻击面。babycat这种题攻击面非常小,唯一的入口就是那个name参数,所以后面所有精力都应该集中在参数测试上。2. 第一波试探:输入框背后的参数拼了什么命令2.1 基线请求与URL参数识别先建立基线请求,用浏览器地址栏或者抓包工具发送一个正常的请求:GET /?name=abc HTTP/1.1 Host: 目标靶机返回内容是Cat abc not found.。这个abc原样出现在响应里,说明服务端确实在处理这个参数,并且很可能是把参数值直接拼到了某段输出逻辑中。我尝试把abc改成abc def,URL编码后是abc%20def,响应里的Cat后面出现空格,变成了Cat abc def not found.。这说明参数值会被原样拼接到字符串里。此时有两种典型可能:第一种是PHP代码类似echo "Cat $name not found.";,仅仅是字符串拼接,没有命令执行;第二种是后端把$name拼到了shell命令里,比如cat $name,执行失败后的错误信息被捕获并输出。区分这两种情况,最直接的办法是输入带有shell语义的字符,比如;、|、&、$等。2.2 从报错确认shell_exec我输入?name=;ls,页面返回了两行内容,一行是ls: cannot access '': No such file or directory,另一行是一串文件名,包括index.php、flag、styles.css。这里其实有两个意外收获:第一,命令注入存在;第二,当前目录下居然有一个叫flag的文件,甚至不需要去读根目录下的/flag。再输入?name=;cat flag,我以为会直接拿到flag,没想到页面返回sh: 1: cat: not found。这就很奇怪了,明明ls能执行,cat却不存在。仔细看一下报错,sh: 1: cat: not found说明环境变量有问题,或者cat命令被重命名、移除了。为了确认,我输入?name=;which cat和?name=;which ls,发现ls在/bin/ls,而which cat没有任何输出,连错误提示都没有。这基本确定是容器环境里没有cat,或者cat被出题人用某种方式隐藏了。后来我才意识到,这道题叫“babycat”,但环境里没有cat命令,出题人是在逼选手换命令。常见替代命令有tac(倒序输出,但读普通文件足够)、more、less、head、tail、nl、awk等。这个知识点如果平时没积累,临场很容易卡住。2.3 源码泄露把答案送到眼前顺着命令执行的思路,我尝试读取源码:?name=;ls -la,输出里有一个index.php,权限是-rw-r--r--,可以直接访问。我试着用命令把它打印出来:?name=;head index.php。head命令是存在的,所以成功输出了源码。源码不长,核心代码如下:<?php $name = $_GET['name']; if (preg_match('/\/|cat|more|less|tail|\s|flag/i', $name)) { die('forbidden'); } $cmd = "cat " . $name . " 2>/dev/null"; $output = shell_exec($cmd); echo $output ? $output : "Cat $name not found."; ?>这段源码信息量很大。参数$name被直接拼进cat命令,用的是shell_exec执行。过滤规则是preg_match匹配以下模式:斜杠/、字符串cat、more、less、tail、空白字符\s、字符串flag。也就是说,斜杠、空格、常见的读文件命令以及flag关键字都被过滤了。我前面输入;ls之所以成功,是因为没有触发这些规则;输入;cat flag失败,一方面因为没有cat命令,另一方面也触发了flag关键字过滤,即使有cat也会被拦截。从源码还能看出,2>/dev/null被拼在命令末尾,所以命令执行时的报错信息会被丢弃,页面只展示命令的标准输出。这解释了为什么cat不存在时没有直接看到bash: cat: command not found,而sh: 1: cat: not found是来自另一个分支——可能是shell_exec返回时由环境抛出的。不管细节如何,至少现在我们拿到了源码,接下来的利用就有明确方向了。3. 命令注入的三道过滤:空格、敏感词和斜杠3.1 分号注入打通第一层源码里的拼接方式是cat " . $name . " 2>/dev/null,那么当我提交;ls时,完整命令变成:cat ;ls 2>/dev/nullShell会先执行cat,但因为没给参数,cat从标准输入读取,在Web环境下标准输入立刻到达EOF,于是cat干净退出,后面紧跟的ls正常执行。ls的标准输出被返回,所以我能在页面上看到文件列表。这里的关键是:分号没有被过滤,所以整个命令可以被分隔成多条子命令。也许有人会问,为什么不直接用管道|?源码确实没有过滤|,但我测试时发现管道符在反引号场景下容易和后面命令的后缀拼接混淆,而且分号更直观,能明确看到每一步执行结果。在真实题目中,优先尝试分号是完全合理的思路。3.2 把过滤规则测出来拿到源码后,我决定把过滤规则完整测一遍,避免瞎猜。下表是我当时的测试记录:Payload预期实际响应?name=;ls执行ls返回文件列表?name=;cat flag读取flag返回forbidden?name=;tac flag用tac读flag返回forbidden(触发了flag关键字)?name=;tac fla*通配符读flag返回forbidden(存在空格,触发\s过滤)?name=;tac${IFS}fla*无空格+通配符成功输出flag这个表格非常直观:第一层被flag挡住,第二层被空格挡住,第三层被cat这个词本身挡住。我们需要分别绕过三个限制。3.3 组合绕过拿到flag逐个拆解:空格过滤:用${IFS}代替空格。IFS是Shell里的内部字段分隔符,默认包含空格、制表符和换行,${IFS}在命令展开时会变成空格,而且不会触发\s正则匹配。所以tac${IFS}fla*等价于tac fla*。cat被过滤:简单粗暴换命令,tac是cat的反向输出,同样能读文件。正则匹配的是连续的cat字符串,tac里面没有连续出现cat,所以通过。flag关键字被过滤:用通配符fla*代替,Shell在执行时会把它展开成当前目录下所有以fla开头的文件,正好匹配flag。正则匹配的是flag这个完整字符串,fla*里没有,所以也通过。最终payload是:?name=;tac${IFS}fla*完整命令变成:cat ;tac${IFS}fla* 2>/dev/null其中cat空读后退出,tac${IFS}fla*展开为tac fla*,由于没有空格,成功绕过了过滤,页面上直接输出flag内容。这道题到这里就结束了。顺便提一句,如果flag不在当前目录而在根目录,还需要处理斜杠过滤。一个常见思路是用环境变量拆出斜杠,比如${HOME:0:1}可能得到/,但需要确认环境变量。另一个更通用的做法是避免输入斜杠,把文件移到当前目录不太现实,所以很多命令注入题把flag放在当前目录是有意降低难度。babycat显然属于这种送分类。4. 如果题目不按套路走:文件包含与伪协议的并行路线4.1 为什么命令注入经常和文件读取混着考打CTF的Web题时经常会发现,命令注入和文件读取在现象上非常接近:都是通过控制一个参数去读文件内容。区别在于底层函数——命令注入走的是system、shell_exec、exec这些Shell执行函数,文件读取走的是file_get_contents、readfile、include这些文件操作函数。题目名字叫babycat,核心线索又是cat命令,所以命令注入是首选方向。但如果某道题隐藏了源码,新手往往很困惑:输入;ls没有任何反应,输入../index.php反而能看到源码。这时候就要立刻切换思路:也许后端根本没进Shell,只是在做文件包含或文件读取。继续把命令注入想下去只会浪费时间。4.2 php://filter 读源码的具体操作假设babycat改成这样:参数$name被拼进include($name . '.html'),那么直接读index.php会触发PHP解析,看不到源码。此时需要用PHP流包装器,把文件内容做base64编码后再输出,常见payload是:?name=php://filter/convert.base64-encode/resource=index.php页面会输出一串base64,解出来就是完整源码。这个技巧和高阶命令注入一样,本质都是绕过“直接输出”的限制。虽然babycat不需要走这条路,但如果你不想只做一个只会复制payload的人,应该理解为什么convert.base64-encode能读到源码:它让包含的文件内容先经过过滤器,再被输出,PHP不会把结果当作代码执行。4.3 两条路线的判别方法实操中的快速判别方法很简单,推荐大家记一下:提交?name=;ls,如果响应里有目录列表或命令报错,说明命令注入成立。提交?name=../../etc/passwd,如果响应里出现root:x:0:0这样的内容,说明文件读取成立。提交?name=php://filter/convert.base64-encode/resource=index.php,如果响应变成一串base64字符,说明存在include/require且可以直接读源码。这三种palyoad覆盖了Web入门阶段的三大核心漏洞:命令注入、目录穿越、文件包含。babycat命中第一种,但很多变体题是把三者混在一起考,所以最好都在脑内形成一个固定判断流程。5. 复盘与延伸:从babycat到命令注入全家桶5.1 实战中的两个思维误区第一个误区:以为所有命令注入都得靠cat读文件。babycat这个环境里没有cat,直接导致我一开始怀疑题目是否根本不是命令注入。后来才意识到,命令注入的第一原则是“环境里有什么命令用什么命令”,而不是“我背过什么payload就用什么payload”。ls、tac、head、nl甚至php都能读文件,遇到cat缺失时,先执行ls /bin看看有哪些可用命令。第二个误区:被过滤逻辑挡住后,只想着大小写绕过,没想到通配符和Shell展开。比如flag被过滤,很多人会尝试FLAG、fLaG,但正则一旦保险起见加了i修饰符,大小写就完全没用。而fla*是Shell展开,在正则过滤的那一刻还只是一个普通字符串,根本不包含flag,只有在Shell执行时才会变成文件名。这个时间差是绕过关键字过滤的经典手法。5.2 命令注入的绕过工具箱我根据平时刷题经验整理了一个小型工具箱,babycat里用了前几项,后面的也建议背下来:过滤点绕过方法示例空格${IFS}、$IFS$9、%09(URL制表符)tac${IFS}flag关键字换命令、通配符、字符串拼接tac fla*、c""at flag斜杠环境变量拆解、$PATH替换${PATH:0:1}flag分号/管道换行符%0a、$()、反引号?cmd=ls%0acat flag命令回显用curl外带、DNS查询、写文件再访问适合无回显场景这里要特别提醒:外带数据在正规授权靶场练习没问题,但绝对不要对着未授权目标做,否则性质完全不同。CTF题目本身就是虚拟靶机,练习时注意边界即可。5.3 新手练习命令注入的正确姿势想真正吃透命令注入,建议自己用Docker搭一个最小靶场。比如在本地写一个cmd.php:<?php $cmd = $_GET['cmd']; system($cmd); ?>然后挂在Nginx或Apache下,反复提交各种payload,观察每个字符是如何影响最终命令的。只有亲手执行过;whoami、|whoami、$(whoami)、反引号构造的差异,才能真正理解Shell的解析顺序。光看WriteUp永远记不牢。刷题平台方面,选那种有官方题解和WriteUp沉淀的社区题,一道题做完后对比别人的思路,比闷头做十道重复题效率高得多。babycat这种送分题,价值恰恰在于让你把命令注入的基础链路完整跑一遍:确认注入、测过滤、组合绕过、拿到flag、复盘发散。说实话,这题迷惑性在于名字和页面的第一印象,但只要抓住“cat”这个双关提示,后面的每一步都有迹可循。我在拿到flag之后又把源码读了一遍,发现这题真正想教给新手的不是某个高深技巧,而是在遇到过滤时保持思路灵活:命令不够换命令,字符被过滤就找等价替代。这个能力在后面的Web题目里会用得越来越频繁。