在 Linux 上,PHP 执行系统命令主要通过以下几种函数:
函数 | 描述 |
---|---|
exec() |
执行外部程序,不输出结果,结果可存入变量。 |
shell_exec() |
执行命令并返回完整输出结果(字符串)。 |
system() |
执行命令并立即输出结果。 |
passthru() |
执行命令并直接输出原始数据,适合二进制输出(如视频、图片处理)。 |
`command` (反引号) | 和 shell_exec() 类似,执行命令并返回结果。 |
string exec ( string $command [, array &$output [, int &$return_var ] )
说明: exec执行系统外部命令时不会输出结果,而是返回结果的最后一行。如果想得到结果,可以使用第二个参数,让其输出到指定的数组。此数组一个记录代表输出的一行。即如果输出结果有20行,则这个数组就有20条记录,所以如果需要反复输出调用不同系统外部命令的结果,最好在输出每一条系统外部命令结果时清空这个数组unset($output),以防混乱。第三个参数用来取得命令执行的状态码,通常 执行成功都是返回0。
<?php
// 输出运行中的 php/httpd 进程的创建者用户名
// (在可以执行 "whoami" 命令的系统上)
echo exec('whoami');
?>
string system ( string $command [, int &$return_var ] )
说明: system和exec的区别在于,system在执行系统外部命令时,它执行给定的命令,输出和返回结果。成功则返回命令输出的最后一行, 失败则返回 FALSE。第二个参数是可选的,用来得到命令执行后的状态码。
<?php
$res = system("pwd",$result);
print $result;//输出命令的结果状态码
print $res;//输出命令输出的最后一行
?>
关于第二个参数结果状态码的简单介绍:
如果返回0是运行成功,
在Bash中,当错误发生在致命信号时,bash会返回128+signal number做为返回值。
如果找不到命令,将会返回127。
如果命令找到了,但该命令是不可执行的,将返回126。
除此以外,Bash本身会返回最後一个指令的返回值。
若是执行中发生错误,将会返回一个非零的值。
Fatal Signal : 128 + signo
Can't not find command : 127
Can't not execute : 126
Shell script successfully executed : return the last command exit status
Fatal during execution : return non-zero
string shell_exec ( string $cmd )
说明: 直接执行命令$cmd,将完整的命令输出以字符串的方式返回。如果执行过程中发生错误或者进程不产生输出,则返回 NULL。
所以,使用本函数无法通过返回值检测进程是否成功执行。 如果需要检查进程执行的退出码,请使用 exec() 函数。
<?php
$output = shell_exec('ls -lart');
echo $output";
?>
void passthru ( string $command [, int &$return_var ] )
说明: passthru与system的区别,passthru直接将结果输出到游览器,不返回任何值,且其可以输出二进制,比如图像数据。第二个参数可选,是状态码。
同 exec() 函数类似, passthru() 函数 也是用来执行外部命令(command)的。 当所执行的 Unix 命令输出二进制数据, 并且需要直接传送到浏览器的时候, 需要用此函数来替代 exec() 或 system() 函数。 常用来执行诸如 pbmplus 之类的可以直接输出图像流的命令。 通过设置 Content-type 为 image/gif, 然后调用 pbmplus 程序输出 gif 文件, 就可以从 PHP 脚本中直接输出图像到浏览器。
<?php
header("Content-type:image/gif");
passthru("/usr/bin/ppm2tiff /usr/share/tk8.4/demos/images/teapot.ppm");
?>
原型: 反撇号 执行系统外部命令,相当于 shell_exec
说明: 在使用这种方法执行系统外部命令时,要确保shell_exec函数可用,否则是无法使用这种反撇号执行系统外部命令的。
<?php
echo `dir`;
?>
PHP 执行系统命令 极易受到命令注入攻击,尤其是当用户输入未经处理被拼接到命令中。
攻击示例(命令注入):
$user = $_GET['name'];
shell_exec("ping $user"); // 用户输入 "127.0.0.1; rm -rf /"
escapeshellarg()
和 escapeshellcmd()
是 PHP 提供的两个专门用于避免 命令行注入 的函数,但它们的功能和用途不同。下面我详细解释它们的原理和安全性保障机制。
escapeshellarg()
—— 用于参数$user = escapeshellarg("127.0.0.1; rm -rf /");
shell_exec("ping $user"); // 实际执行:ping '127.0.0.1; rm -rf /'; 这是一个合法的参数,而不是两个命令,`rm -rf /` 不会被执行 ✅
它会把参数整体用单引号包裹(即 '参数'
)
并把内部的单引号('
)转义成安全格式(在 Linux 下是 '\'\''
)
把参数当成纯文本处理,终端不会再解释其中的任何命令字符(如 ;
, &
, |
, >
等)
有效防止 命令拼接注入攻击
✅ escapeshellcmd()
—— 用于整条命令
$cmd = escapeshellcmd("ls; rm -rf /");
shell_exec($cmd); // 实际执行:ls\ rm\ -rf\ /
它会在命令中的特殊字符前添加反斜杠 \
转义,如 &
, ;
, |
, >
, <
, !
等。
使这些字符在 shell 中不会被当作命令控制符处理。
注意它不会加引号
不能用于处理用户输入的数据参数!(比如文件名、路径、IP地址)
$input = $_GET['host'];
shell_exec("ping " . escapeshellcmd($input)); // ❌ 仍然可能注入
$input = $_GET['host'];
shell_exec("ping " . escapeshellarg($input)); // ✅ 参数应该用 escapeshellarg
特性 | escapeshellarg() | escapeshellcmd() |
---|---|---|
用途 | 保护参数 | 保护命令行(不含参数) |
行为 | 用单引号包裹整个参数 | 转义命令中的特殊字符 |
安全性 | ✅ 高(建议用于用户输入) | ⚠️ 低(不要用在用户参数上) |
$host = $_GET['host']; // 用户输入
$host = escapeshellarg($host); // 转义成安全参数
$cmd = '/bin/ping -c 4 ' . $host; // 拼接命令
$output = shell_exec($cmd);
echo "<pre>$output</pre>";
$allowed = ['google.com', 'example.com'];
$host = $_GET['host'];
if (in_array($host, $allowed)) {
$host = escapeshellarg($host);
shell_exec("ping -c 4 $host");
}
exec
类函数(高安全场景)php.ini
中禁用高风险函数:disable_functions = exec,shell_exec,system,passthru,popen,proc_open
/etc/sudoers
,只允许 PHP 执行有限命令:www-data ALL=(ALL) NOPASSWD: /usr/bin/safe-command