资讯动态

从Exec1看命令注入:CTF入门RCE实战解析

发布时间:2026/9/25 3:55:21 来源:尧图企业网站定制
1. 题目概览先搞清楚 Exec1 到底在考什么BUUCTF 上的 [ACTF2020 新生赛] Exec1 是一道很典型的 Web 方向入门题核心考点就四个字命令执行。更准确地说是命令注入Command Injection。题面本身不复杂一个在线 Ping 检测工具输入一个 IP 地址后端把输入拼进系统命令执行然后回显 ping 的结果。很多第一次打 CTF 的人看到这个页面会犯迷糊这不就是个普通的 ping 工具吗怎么跟 flag 扯上关系问题恰恰出在“拼接”这两个字上。ACTF2020 是某高校战队办的新生赛题目定位很明确就是给刚接触 CTF 的新手铺路。Exec1 的 “Exec” 指的是命令执行类漏洞“1” 说明这大概率是整个系列的第一题后面可能还有 Exec2、Exec3 之类的进阶版本。BUUCTF 平台上这类题目的环境大多是 Docker 一键部署出题人已经把靶场搭好了我们只需要通过浏览器去访问一个 URL就能进入题目页面。这道题适合什么人做只要你有一点 Linux 命令基础哪怕完全没接触过 CTF也可以拿它当 RCE 方向的第一个练手靶场。做完它你基本上能理解“用户输入如何变成系统命令的一部分”这个 Web 安全里最核心的问题之一。接下来我会按我实际打题的顺序把从信息收集、注入点判断、payload 构造到最终读取 flag 的完整过程拆开来讲包括中间踩过的坑和一些绕过滤规则的技巧。2. 核心考点命令注入漏洞原理与 Payload 设计2.1 命令注入和 SQL 注入本质上是同一个问题先说原理。命令注入漏洞的产生原因特别直白后端代码把用户输入直接拼到了系统命令里然后交给 shell 去执行。比如题目的服务端逻辑大概长这样?php $ip $_GET[ip]; system(ping -c 4 . $ip); ?这段代码的本意是接收一个 IP 参数然后 ping 这个地址。问题在于$ip完全没做过滤用户输入什么就拼接成什么。如果你传进去的是127.0.0.1;ls那么最终 shell 执行的就是ping -c 4 127.0.0.1;ls分号左边的 ping 指令正常执行分号右边的ls也会跟着执行。这种利用方式和 SQL 注入的思路是一模一样的开发者的代码信任了用户输入把输入内容当成了代码的一部分去解释执行。区别只是 SQL 注入拼接的是 SQL 语句命令注入拼接的是系统命令。搞懂了这一点你就明白为什么安全开发里总强调“永远不要信任用户输入”。2.2 几个拼接符号的语义差异一定要分清做命令注入题Payload 里最常用的拼接符号有四个分号;、管道符|、逻辑与、逻辑或||。很多入门教程会直接告诉你用;但如果只背结论不看语义遇到过滤规则就会很被动。分号;表示顺序执行不管前一条命令成功还是失败后一条都会执行。管道符|表示前一条命令的 stdout 作为后一条命令的 stdin 输入。表示前一条命令必须执行成功后一条才会执行。||恰好相反前一条命令失败了后一条才执行。它们之间的差异在 ping 场景里很明显127.0.0.1;ls # 先 ping再 ls两条都执行 127.0.0.1|ls # 把 ping 的输出丢给 ls 处理ls 大概率会解析一堆没意义的内容 127.0.0.1ls # 如果 ping 成功执行 ls 127.0.0.1||ls # 只有 ping 失败才执行 ls比如输入一个不存在的主机名本题最稳妥的是分号。但实战里 WAF 可能拦分号这时候你可以试管道符、换行符%0a甚至是和||的变体。多了解几种符号等于多几个绕过思路。2.3 为什么前面一定要先拼一个合法 IP新手最容易犯的一个错误是直接输入ls或者cat /flag结果页面显示ping: unknown host一脸懵。原因在于后端拼出来的命令是ping -c 4 lsshell 把ls当成主机名去解析了压根不会执行列目录的操作。正确的姿势是先给一个合法 IP 保证 ping 命令能顺利执行再用拼接符号把我们的命令带出来。这个思路可以总结成一句话“合法参数 拼接符号 恶意命令”。在本题里就是127.0.0.1;ls。先让 ping 正常工作分号后面的命令才会被 shell 解释执行。这个套路不仅适用于这道题后面做任何命令注入题目第一反应都应该是先构造一个能正常执行的基底再去叠加攻击命令。3. 完整解题过程一步步拿到 Flag3.1 第一步验证注入点是否存在打开题目页面后环境信息大概是这样一个输入框一个提交按钮提示语通常是“输入 IP 地址进行 Ping 检测”。我先输入127.0.0.1提交页面正常输出了 ping 的完整回显说明后端确实执行了 ping 命令。接下来验证注入点。我提交127.0.0.1;whoami如果页面在 ping 的输出后面多了一行用户信息比如www-data那就说明分号后面的命令被成功执行。这里还可以顺手提交127.0.0.1;ls -la看当前目录结构进一步确认是哪种系统环境。如果ls能跑而dir报错基本可以判定后端是 Linux反过来如果能用dir那可能是 Windows 环境。这道题环境默认是 Linux权限通常也给了www-data但命令注入题里权限一般都不低直接读文件通常没有问题。我实操的时候输入127.0.0.1;whoami返回了www-data注入点确认无疑。到这一步这道题最核心的部分已经拿下了你可以在靶机上执行任意系统命令。3.2 第二步定位 Flag 文件在哪注入点确认后目标就变成找到 flag 文件并读取内容。不少入门者在这里会钻进一个误区只在当前目录瞎转悠不停ls、cat当前目录下的每个文件。其实 CTF 出题人经常把 flag 放在根目录或者放在一个不起眼的路径下全盘搜索往往比手动翻目录高效得多。我当时的做法是先看根目录127.0.0.1;ls /回显里能看到flag文件直接在根目录下。但有时候 flag 文件名不叫flag而是叫flag.txt、flag.php、flag_12345这类变体所以更通用的搜索命令是这样127.0.0.1;find / -name *flag* 2/dev/null这条命令会从根目录往下全盘搜索文件名带 flag 的文件同时把权限不足的报错信息丢弃。执行结果可能有一堆但真正有用的通常就是/flag或者/var/www/html/flag.txt这类路径。如果目标文件在/var/www/html下说明它和 Web 服务在同一个目录出题人可能想让你通过访问 URL 读取但大多数情况下用命令直接读取就好。3.3 第三步读取 Flag 并提交找到文件路径之后读取内容就很简单了127.0.0.1;cat /flag回显里会出现一个标准的 CTF flag 字符串格式通常是flag{...}。把这一串复制到 BUUCTF 的提交框里题目就算打通了。这道题到拿 flag 为止就结束了没有二次绕过没有特殊过滤属于非常标准的入门体验。但我要强调一个细节读取 flag 前最好先确认一下文件类型。如果你用cat读一个二进制文件终端会直接花屏这不算大问题但如果你读的是一个 PHP 文件它可能被服务端解析后不返回源码导致你什么都看不到。稳妥的做法是先file /flag看类型再用cat读。3.4 请求方式的小细节能用 curl 就别手敲浏览器实际操作时很多人喜欢直接在浏览器地址栏里输入完整 Payload比如http://target/?ip127.0.0.1;cat /flag浏览器会帮你把空格、分号、斜杠做 URL 编码然后在服务端解码。一般情况下没问题但如果你要执行的命令很长或者包含特殊字符比如{}、$、#、地址栏写起来会很痛苦还可能被某些浏览器插件或代理干扰。我的习惯是用 curlcurl -X GET http://target/?ip127.0.0.1;cat%20/flag或者用更稳妥的 POST 方式配合--data-urlencode让 curl 自动完成 URL 编码curl -X POST http://target/ --data-urlencode ip127.0.0.1;cat /flag --data-urlencode submitping使用 Burp Suite 也很方便抓包后直接在 Repeater 里改参数可以反复尝试各种 Payload。这个习惯对于后面做更复杂的 RCE 题目非常重要因为手工在浏览器里敲命令一旦出错定位问题要花更多时间。4. 变体与进阶从 Exec1 延伸到更多命令执行场景4.1 无回显怎么办延时探测与写文件Exec1 这个页面是有回显的所以做起来很舒服。但真实比赛里很多命令注入是“盲注”状态命令执行了页面却一点输出都没有。这时候就要换思路核心是找一个外部可观测的通道来验证命令是否执行。最朴素的验证方式是用延时。提交127.0.0.1;sleep 5然后掐表看响应时间。如果页面隔了 5 秒才返回说明sleep命令成功执行。在此基础上还可以进一步做数据外带比如把命令执行结果写入 Web 目录下再用浏览器访问那个文件127.0.0.1;ls / /var/www/html/out.txt然后访问http://target/out.txt如果能看到目录列表说明写入成功命令也有回显通道了。再比如用 DNS 外带的方式把 flag 内容拼到域名里发出去再由自己的服务器接收不过这道题用不上这么复杂的手段知道思路即可。4.2 空格被过滤时的绕过套路题目如果简单直接敲空格就行。但很多靶场和实战 WAF 会把空格视为高危字符直接拦掉。这时候最常见的替代品是${IFS}即国际字段分隔符Internal Field Separator环境变量默认包含空格、Tab 和换行。在 shell 里把${IFS}放到原本空格的位置效果等同127.0.0.1;cat${IFS}/flag有些更严格的环境会对${IFS}本身做过滤这时候可以尝试${IFS}$9或者直接用 Tab 字符URL 编码为%09127.0.0.1;cat%09/flag再不行就试$IFS不带花括号。绕过方式没有一定之规关键是要理解 shell 对特殊变量的解析规则然后在不同过滤规则之间来回试。4.3 关键字被过滤时的三个常用手段过滤规则如果连cat、flag这种关键字都不放过就需要动用 shell 的特性来拆解字符串了。第一种是单引号和反斜杠拆字cat、c\at在 shell 解析后都会变成cat如果后端只是简单地把cat字符串替换为空这种写法就能绕过。第二种是参数扩展ac;bat;$a$b /flag第三种是 base64 编码后管道执行echo Y2F0IC9mbGFn | base64 -d | shY2F0IC9mbGFn是cat /flag的 base64 编码解码后由sh执行。这个方法的优点是整个命令字符串里没有出现cat和/flag关键字适合对付关键词黑名单。但要注意base64 -d在某些精简镜像里可能不存在目标环境如果是 Alpine Linux 或特殊容器可能会报错最好先确认基础命令是否可用。4.4 防御视角这道题在真实开发中怎么修打完一道题我会习惯性想一想开发侧应该怎么堵住这个洞。Exec1 的修复方法在面试里也常被问到。第一层是输入校验比如用正则限定只能输入 IPv4 地址这样;ls这种内容根本进不了命令。第二层是命令调用方式PHP 里可以用escapeshellarg()对参数做转义或者干脆不用system()、exec()这类函数改成直接用网络库的 ICMP 功能从根源上避免拼接 shell 命令。第三层是权限控制Web 服务使用低权限用户运行即使被注入能读到的文件也有限。这道题里直接cat /flag能成功说明容器权限给得比较宽松真实环境一般不会这么大方。4.5 做完之后还能练什么Exec1 是一道很好的起点题做完它你可以顺着命令执行这个方向继续刷题。BUUCTF 上搜“Exec”或者“RCE”能找到一批类似题目难度从有回显到无回显、从无过滤到有过滤逐渐递进。还可以试试 ACTF2020 新生赛系列里的其他 Web 题比如上传相关的题目思路会从 RCE 转到文件上传和 Web Shell 方向。如果想把命令注入做得更扎实建议多了解 Linux shell 的变量展开、管道、重定向、通配符这些基础能力它们不仅是打 CTF 的工具也是日常运维排错的底层功。我个人做这道题最大的体会是命令注入不靠背 Payload靠理解“拼接”这件事。你只要知道服务端是把输入原样塞进命令里就能顺着“合法参数 拼接符号 恶意命令”的框架自己推出各种玩法。Exec1 把这条链路完整演示了一遍新手只需要照着我上面的步骤多操作几次很快就能建立 RCE 方向的手感。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑