ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Ping Ping Ping 命令注入实战:从空格绕过到关键字过滤的 CTF 通关思路

Ping Ping Ping 命令注入实战:从空格绕过到关键字过滤的 CTF 通关思路 BUUCTF 平台上挂着的一道 [GXYCTF2019]Ping Ping Ping算是我见过最适合入门 Web 命令注入的题目之一。界面简单到不能再简单就是给你一个输入框让你填 IP填完以后页面会模拟 ping 命令把结果回显出来。可就是这么个“玩具题”把命令注入里最常出现的两个过滤点——空格过滤和关键字过滤——全塞进去了而且过滤方式干净利落没有多余的花活。整道题不用扫描器、不用猜路径纯手工就能打穿非常适合刚接触 CTF Web 方向、想搞明白“命令注入到底是怎么绕过过滤”的同学练手。我这篇 write up 不打算只贴 payload而是把每一步的思考过程、为什么这么绕、绕的时候踩了哪些坑都展开聊。毕竟命令注入这类题背十个 payload 不如真正理解 shell 的解析机制一次理解了以后遇到再怎么变种的过滤你都能现场推出来。1. 题目概览与考点拆解1.1 题目长什么样打开靶机页面只有一个输入框名字叫“Ping Ping Ping”提示你输入 IP 地址。你输入127.0.0.1页面就会返回类似这样的内容PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq1 ttl64 time0.023 ms 64 bytes from 127.0.0.1: icmp_seq2 ttl64 time0.043 ms 64 bytes from 127.0.0.1: icmp_seq3 ttl64 time0.037 ms 64 bytes from 127.0.0.1: icmp_seq4 ttl64 time0.045 ms一看这个交互形式几乎可以断定后端是把用户输入拼到了系统命令里最常见的就是这种$cmd ping -c 4 . $_GET[ip]; exec($cmd, $output);关键问题在于这个拼接有没有过滤、过滤了什么、怎么绕过去。整道题的考点也就从这里展开。1.2 核心考点拆解这道题表面上是“Ping”实际上考的是 Linux 命令注入而且考得非常经典核心知识点可以拆成四块第一命令拼接与注入点识别。你要能判断出参数是直接被拼接到 shell 命令后面的并且知道用;、|、、、换行符这些符号来闭合前面的“ping -c 4”部分把自己想要的命令接上去。第二空格过滤绕过。后端对输入里的空格做了过滤直接写cat index.php会被拦。Linux 的 shell 里有很多东西可以替代空格比如$IFS、${IFS}、Tab、重定向符这些都属于 shell 本身的语法特性过滤脚本可能没考虑到。第三关键字过滤绕过。flag这个字符串被直接屏蔽了所以你不能在输入里出现flag。绕过思路包括变量拼接、通配符匹配、命令替换、base64 编码管道执行等。第四源码审计意识。这道题真正的突破口不是盲猜而是先想办法把index.php的源码读出来看清楚过滤规则再针对性构造 payload。很多新手上来就硬试flag被拦了十几次还在试这就是没有“先看规则再打”的意识。后面我会详细说。2. 信息收集从正常 ping 到摸清过滤规则2.1 正常参数与第一次注入尝试先用127.0.0.1测一下确认回显正常。接着试注入常见的闭合符号就是;因为分号在 Linux shell 里表示“前一条命令执行完接着执行后一条”最直观。尝试?ip127.0.0.1;ls返回结果里除了 ping 的输出还多了当前目录的文件列表flag.php index.php这一步很关键说明命令注入是通的ls成功执行了。同时也确认了目标文件就叫flag.php那么下一步自然是想办法把它读出来。有人可能会问为什么ls没被过滤因为ls这条命令本身就是纯字母没有空格不触发过滤规则。但如果你想执行cat index.php命令里带了空格就立刻会被拦住。所以下一步的关键是绕过空格。2.2 读取 index.php 源码拿到过滤规则面对一个未知过滤规则的题目我的习惯是先读源码再决定怎么打。既然需要读index.php而空格被过滤了那这里先直接用最常见的空格替代品$IFS$9?ip127.0.0.1;cat$IFS$9index.php返回结果里会出现一段 PHP 代码核心部分大概是?php if(isset($_GET[ip])){ $ip $_GET[ip]; if(preg_match(/flag|\ /, $ip)){ die(fxck your flag!); } $cmd ping -c 4 . $ip; exec($cmd, $out); foreach($out as $k$v){ echo $v.br; } } ?到这里过滤规则一目了然正则/flag|\ /只过滤了两样东西——字符串flag和空格。注意这个正则没有过滤分号、没有过滤$、没有过滤反引号、没有过滤|、没有过滤?、也没有过滤*这就给后面留了特别多的操作空间。2.3 过滤规则分析空间比你想象的大很多人看到“过滤了 flag 和空格”就懵了觉得啥都干不了。实际上仔细拆一下这个正则能发现它非常宽松空格被过滤意味着你不能直接写cat flag.php但可以用 Linux shell 的多种语法特性来替代空格。flag字符串被过滤意味着输入里不能直接出现这四个字母但 shell 变量拼接、通配符展开、命令替换这些机制都能让“flag 字符”在执行阶段才出现而正则检查的是原始输入字符串根本管不到执行阶段发生的事情。更关键的是这个正则没有过滤$所以$IFS、$()、$9全都能用。没有过滤|所以管道符也可以随便用。没有过滤?和*所以通配符这扇大门也敞开着。可以说出题人故意留了一道口子考察的就是你有没有吃透 shell 的解析顺序。3. 空格绕过$IFS 系全家桶3.1 为什么一个空格就能拦住大多数人空格在 shell 里的作用是分隔参数比如cat index.php空格告诉 shell 左边是命令右边是参数。如果过滤脚本把输入里的空格全部杀掉那你写的命令在 shell 眼里就变成了catindex.php系统会去找一个叫catindex.php的命令自然执行失败。但问题是shell 本身并不只有空格这一种方式来分隔命令和参数。Linux 的 shell 语法里$IFSInternal Field Separator内部字段分隔符就是用来决定“怎么把一行字符串拆成多个字段”的默认值包含空格、Tab 和换行。所以只要用$IFS顶替空格shell 在解析时就会把它当成字段分隔符处理效果完全一样。3.2 空格替代方案横向对比实测下来可用的空格替代方案有下面几个我整理成表格方便对比方案写法原理实测结果$IFS$9cat$IFS$9index.php$IFS是默认分隔符$9是第 9 个位置参数通常为空用来把$IFS和后面的文件名隔开推荐最通用${IFS}cat${IFS}index.php花括号明确变量名边界防止 shell 把IFSindex当成变量名可用效果稳定单个$IFScat$IFSindex.php理论可行但 shell 会尝试解析$IFSindex这个变量名可能解析失败不推荐容易翻车Tab 字符cat%09index.phpTab 本身也是分隔符URL 编码为%09部分环境可用不通用输入重定向catindex.php表示把文件内容作为标准输入传给 cat不需要空格可用但部分命令/场景受限花括号展开{cat,index.php}bash 的花括号展开会把逗号解释成空格依赖 bashdash 下会失效这里重点说下为什么我推荐$IFS$9。单独用$IFS的时候它后面紧跟的字符会跟IFS连在一起被 shell 尝试解析成一个新的变量名比如cat$IFSindex.php会被解析成$IFSindex而这个变量不存在于是展开为空最终命令就变成了catindex.php直接报错。加一个$9第 9 个位置参数在没传参的情况下它展开为空字符串作用就是隔断变量名边界让 shell 先解析$IFS再解析$9最后得到正确的空格分隔效果。这个细节不理解的话光背$IFS$9是背不明白的。3.3 嵌套命令里的空格怎么处理读index.php只是第一步后面读flag.php的时候payload 里往往还带嵌套命令比如反引号ls、$()命令替换里面也可能出现空格。这时候要注意嵌套部分的空格同样要用$IFS$9替代。举个例子如果你想执行cat $(ls)里面的$(ls)没有空格没问题但如果你想执行cat $(echo flag.php)那echo和flag.php之间的空格也得换成$IFS$9否则整条命令在用户输入层面就触发了空格过滤。这种嵌套场景最容易踩坑因为有些新手在内外层之间混用了真实空格和$IFS$9导致明明看起来没问题却一直报错。4. 关键字过滤绕过花式读取 flag.php4.1 直接硬闯 flag.php 的下场在读完源码、确认过滤规则之后我一开始还是不死心直接试了?ip127.0.0.1;cat$IFS$9flag.php返回fxck your flag!果不其然flag字符串被正则逮住了。这里要注意一个细节过滤脚本匹配的是原始输入字符串你输入里只要有flag这四个连续字符不管你后面有没有.php、不管它是不是变量名的一部分都会被拦。所以flag不能直接在输入里出现得让它在 shell 执行阶段“拼出来”。4.2 变量拼接法让 flag 在执行时诞生思路很简单既然flag四个字母不能出现那我就把它拆成两半用两个变量分别存执行的时候让 shell 把它们拼起来。?ip127.0.0.1;xfl;yag.php;cat$IFS$9$x$yshell 在执行这一行时会先把$x展开成fl把$y展开成ag.php于是cat$IFS$9$x$y在解析后变成cat flag.php而正则检查的是原始输入xfl;yag.php;cat$IFS$9$x$y里面确实没有flag这个连续字符串所以顺利绕过。这里还有个进阶写法直接用变量名本身来拼?ip127.0.0.1;ag;cat$IFS$9fla$a.php原理一样fla$a在执行时变成flag。我个人的习惯是拆成两个变量x和y逻辑上更清晰不容易写乱。实测下来这个方案最稳也是我最后拿 flag 用的主方案。4.3 通配符法让 shell 自己去补全文件名既然正则不让输入里出现flag那就干脆不写全用通配符让 shell 帮你补全。Linux 的 shell 在处理*和?时会先对文件名进行匹配再把匹配到的结果传给命令。这个展开过程发生在命令执行阶段原始输入里没有flag字符串。单字符通配?ip127.0.0.1;cat$IFS$9fla?.php?匹配任意单个字符fla?匹配到了flag于是 shell 把cat fla?.php展开成cat flag.php再执行。也可以用*?ip127.0.0.1;cat$IFS$9f*.phpf*.php匹配flag.php原理相同。但用*的时候要小心如果目录下有多个以 f 开头且以 .php 结尾的文件cat会把它们全部作为参数比如index.php不会被匹配到但假如以后遇到更复杂的目录结构可能一次会 cat 多个文件。单字符?的匹配范围更可控在文件名长度确定时我更推荐它。4.4 命令替换配合 cat动态获取文件名命令替换的反引号和$()也可以用来绕关键字过滤。比如先用ls列出文件名再把输出作为cat的参数?ip127.0.0.1;cat$IFS$9ls这个 payload 里ls的输出是flag.php index.php于是命令在执行阶段变成cat flag.php index.php两个文件都会被 cat 出来那 flag 自然也就暴露了。$()写法的原理完全一样?ip127.0.0.1;cat$IFS$9$(ls)两个都实测可用。不过要注意这种写法会把当前目录下所有文件都作为参数传给 cat如果目录里文件很多、存在二进制文件输出可能会很乱但刷题场景下问题不大反而能快速摸清楚目录里都有哪些文件。4.5 base64 编码管道执行把命令藏进编码里如果碰到过滤更严格的题目变量拼接和通配符可能都会失效那就上大杀器base64 编码 管道解码 shell 执行。这题的过滤根本没有删除|、没有删除echo、没有删除base64所以这条路也通。先把要执行的命令cat flag.php编码echo cat flag.php | base64得到Y2F0IGZsYWcucGhw然后构造 payload?ip127.0.0.1;echo$IFS$9Y2F0IGZsYWcucGhw|base64$IFS$9-d|sh这条命令在 shell 里执行的过程是先echo输出那串 base64 字符串然后用管道传给base64 -d解码成cat flag.php再通过管道交给sh去执行。整条原始输入里既没有空格全用$IFS$9代替也没有flag字符串所以正则检查直接通过。这个方案最大的好处是通用性极强。不管过滤了哪些关键字只要没把echo、base64、管道符、sh这些基础组件删掉你就能把任意命令打包进 base64 里执行。后面遇到更难的命令注入题这个思路依然非常能打。4.6 其他偏方换行、编码命令与心机技巧除了上面几个主流方案还有一些偏方可以扩展一下思路。换行符%0a在某些题目里可以用来绕过对同一行内容的检查比如?ip127.0.0.1%0acat$IFS$9flag.php如果后端只对$_GET[ip]做了正则检查那换行符后面的内容也在同一个字符串里通常也会被检查这题实测会被拦。但在某些只检查第一行的题目里这个技巧是能用的。命令本身也可以用十六进制或八进制编码写在 shell 命令里比如$\x63\x61\x74表示cat不过这种写法在 CTF 的常规命令注入里用得少而且依赖 bash 的 ANSI-C Quoting不是所有 shell 都支持这里就不展开细讲了。还有一个比较“绕”的思路用grep直接搜文件内容不读整个文件?ip127.0.0.1;grep$IFS$9f$IFS$9fla?.php这个复杂度有点高而且不一定比直接 cat 优雅我只是列出来说明思路可以很多样。4.7 最终拿到 flag用变量拼接法或者通配符法执行成功后页面会输出flag.php的源码?php echo flag{xxxxxxxxxxxxxxxxxxxx}; ?flag{...}就是本题的最终答案。这里要提醒一句不同平台、不同时间的靶机环境 flag 值不一样你打的时候以自己的回显为准。我写这篇 write up 的时候flag 就是直接写在flag.php里的没有套娃、没有二次解密读出来就是答案。5. 常见问题与排查记录5.1 复制 payload 却不生效的原因刷题群里经常有人问“我和 wp 里写的一模一样为什么我的就不出 flag”排在最前面的原因通常是本地 shell 和 URL 层的展开问题。如果你在终端里用 curl 发包直接写双引号包住 URL那你本地 shell 会先把$IFS、$9、$x这些变量展开掉发到服务器上的内容就已经不是 payload 本身了。正确做法是用单引号包住整个 URL或者给$加反斜杠转义curl http://target/?ip127.0.0.1;xfl;yag.php;cat$IFS$9$x$y在浏览器里直接输入时也要注意浏览器可能会自动把某些字符做 URL 编码比如空格变成%20、#变成锚点等。payload 里尽量别用到#真要用的话得编码成%23。另外还有一个很隐蔽的坑有些 payload 末尾会加#注释符来截断后面的内容但在 URL 里#本身就是锚点标识符会被浏览器拦下属于“本地解析吃掉了一部分 payload”的典型问题。5.2 环境和终端差异BUUCTF 的容器不同批次可能跑在不同的基础镜像上有的是 Ubuntu有的是 Debian有的可能是精简版 Alpine。不同发行版的默认 shell 不一样有的是 bash有的是 dash对语法特性的支持也不一样。实测下来$IFS$9和${IFS}在这些环境里都稳定可用但{cat,index.php}这种花括号展开写法在 dash 环境下是无效的因为它是 bash 特有的语法。所以我写 payload 的时候会优先选那些跨 shell 通用的写法这也算是个实战经验不是所有题目环境都是 bash能通用的姿势才是好姿势。base64 -d这个参数在 GNU coreutils、busybox 里基本都支持但极少数精简环境可能只有base64 --decode或者没有 base64 命令真遇到这种情况就换变量拼接或者通配符方案没必要死磕。5.3 信息回显问题这一题的页面会把所有输出拼在 HTML 里导致有些输出被br标签或者其他页面元素遮挡。尤其是读index.php源码的时候如果你用浏览器直接看渲染后的页面可能只看到一部分感觉少了东西。解决办法有两个一个是右键“查看网页源代码”另一个是直接用 curl 在终端里看原始响应后者更干净。还有种情况是 ping 的输出比较长flag 被挤到后面了需要往下翻。我在调试时习惯给 payload 加一个;ls先确认当前目录再决定下一步读哪个文件省得对着输出猜。5.4 建议的调试工具做这类题我强烈建议用 Burp Suite 的 Repeater 而不是纯浏览器。Repeater 可以手动控制每一个字符的编码还能直接看到服务器返回的原始响应包排查编码问题特别方便。如果你不习惯用 Burp那 curl 单引号发包也够用配合-i参数看响应头也能定位很多问题。我自己调试时的习惯是先用 curl 发最简 payload 确认命令能执行比如?ip127.0.0.1;ls再逐步追加复杂度。每次只改一个变量报错了也容易定位是空格问题、关键字问题还是编码问题。6. 这套思路能延伸到哪些地方6.1 常见过滤变体Ping Ping Ping 的过滤规则已经算很温柔了实战里你会遇到更多变体。比如过滤cat关键字可以用tac、more、less、head、tail、nl等替代命令过滤/路径分隔符可以用cd进入目录后用相对路径过滤$那就只能靠通配符和重定向过滤|那就用;或$()来做命令分隔过滤字母那就要考虑base64加sh的管道执行或者把命令写进变量再拼接执行。这题的过滤规则是“输入里出现 flag 就拦截”但很多题目是“输出里包含 flag 就拦”这种就变成命令注入加回显过滤的组合题常见解法是外带数据把命令执行结果通过 DNS 查询、HTTP 请求发到你自己的服务器上。还有一种更恶心的没有任何回显只能靠盲注比如sleep做时间盲注或者用curl外带做带外盲注。6.2 继续刷题方向刷完这一道如果想趁热打铁可以按这个路径走先做同题型的其他命令注入题比如 DVWA 的 Command Injection 模块巩固;、|、的闭合方式然后找一些过滤规则更复杂的靶场比如 SQLi Labs 之外专门练命令注入的题目接着尝试无回显场景自己搭一个只返回“执行成功/失败”的靶机练盲注和外带。最后可以看看真实的 CVE 案例里命令注入是怎么被利用的很多老漏洞的 payload 写法都能在这道题里找到影子。这套“先试注入点、再读源码、后针对性绕过”的流程本质上跟真实渗透测试里的操作习惯是一致的信息收集永远是第一步规则没摸清之前不要盲目打。我个人刷了这么多命令注入题最大的体会是不要背 payload而是理解 shell 的解析顺序。为什么$IFS$9有用因为 shell 先做变量展开再做命令解析。为什么变量拼接能绕过因为正则检查的是静态字符串而 shell 执行的是动态解析后的结果。这些机制搞懂了就算考题把过滤规则换成一百种不同的写法你也能顺着 shell 的特性现场推出来。信息收集的耐心、对 shell 机制的敏感度才是这类题真正的收获。
返回列表