PHP Conference Japan 2024

escapeshellcmd

(PHP 4、PHP 5、PHP 7、PHP 8)

escapeshellcmd转义 shell 元字符

描述

escapeshellcmd(string $command): string

escapeshellcmd() 转义字符串中可能被用来欺骗 shell 命令执行任意命令的任何字符。此函数应用于确保来自用户输入的任何数据在传递给 exec()system() 函数或 反引号运算符 之前进行转义。

以下字符前面都有一个反斜杠:&#;`|*?~<>^()[]{}$\\x0A\xFF'" 仅在它们不配对时才被转义。在 Windows 上,所有这些字符加上 %! 前面都有一个插入符号 (^)。

参数

command

将被转义的命令。

返回值

转义后的字符串。

示例

示例 #1 escapeshellcmd() 示例

<?php
// 我们在这里有意允许任意数量的参数。
$command = './configure '.$_POST['configure_options'];

$escaped_command = escapeshellcmd($command);

system($escaped_command);
?>

注释

警告

escapeshellcmd() 应该用于整个命令字符串,并且它仍然允许攻击者传递任意数量的参数。要转义单个参数,应改用 escapeshellarg()

警告

空格不会被 escapeshellcmd() 转义,这在 Windows 上可能会出现问题,例如路径:C:\Program Files\ProgramName\program.exe。可以使用以下代码片段来缓解此问题

<?php
$cmd
= preg_replace('`(?<!^) `', '^ ', escapeshellcmd($cmd));

参见

添加注释

用户贡献的注释 7 条注释

nicholas at nicholaswilson dot me dot uk
13 年前
需要注意的是,关于 echo 的使用有一个怪癖。如果您有一个要执行的命令,它从 STDIN 获取输入,您通常会这样做

<?php $output = shell_exec("echo $input | /the/command"); ?>

不幸的是,这是一个 *坏主意*,它会使您的脚本不可移植,并在某些系统上产生一个非常难以追踪的错误。根据服务器的设置方式,/bin/sh 将调用 /bin/bash 或 /bin/dash,它们具有非常不同的 echo 版本。永远不要使用 echo;而应使用 printf,它是一致的。如何为 printf 转义?这样做

<?php
$input
= '要精确传递给命令的字符串';
// 只转义 PHP 解析器所需的内容;我们希望
// PHP 在其缓冲区中保存的字符串数据被精确地
// 传递到命令的 stdin 缓冲区。
$cmd = str_replace(array('\\', '%'), array('\\\\', '%%'), $input);
$cmd = escapeshellarg($cmd);

$output = shell_exec("printf $cmd | /path/to/command");
?>

对于偏执狂,此折磨测试验证了 shell 转义和 printf 自身的转义是否正确处理。请放心使用!

<?php

$test
= 'bash 解释的字符串,空格:# & ; ` | * ? ~ < > ^ ( ) [ ] { } $ \ \x0A \xFF. \' " %'.PHP_EOL.
'bash 解释的字符串,无空格:#&;`|*?~<>^()[]{}$\\x0A\xFF.\'\"%'.PHP_EOL.
'bash 解释的字符串,以反斜杠开头:\# \& \; \` \| \* \? \~ \< \> \^ \( \) \[ \] \{ \} \$ \\\ \\\x0A \\\xFF. \\\' \" \%'.PHP_EOL.
'printf 代码:% \ (用于形成 %.0#-*+d,或 \\ \a \b \f \n \r \t \v \" \? \062 \0062 \x032 \u0032 和 \U00000032)';

echo
"这些是我们正在测试的字符串:".PHP_EOL.$test.PHP_EOL;
$cmd = $test;
$cmd = str_replace(array('\\', '%'), array('\\\\', '%%'), $test);
$cmd = escapeshellarg($cmd);

echo
PHP_EOL."这是使用给定转义机制的输出:".PHP_EOL;
echo `
printf $cmd | cat`.PHP_EOL;

echo
PHP_EOL."它们应该完全匹配".PHP_EOL;
?>
carlos at wfmh dot org dot pl dot REMOVE dot COM
14 年前
请注意,它不会转义 !(感叹号)。因此,如果您想例如 printf() 命令以便以后在 shell 中使用(例如通过粘贴到控制台),您需要转义所有感叹号,否则 shell 将尝试将 ! 作为历史引用进行处理。此方法应该足够了

<?php $scaped = str_replace('!', '\!', escapeshellarg( $str ) ); ?>
docey
19 年前
引用命令的主要原因是不能将多个命令连接起来。我不确定这是否是正确的语法,但请记住,这可能会导致一些安全漏洞。以下是如何准确了解您尝试入侵的内容的一种方法。

通常任何 Linux 用户都可以查看几乎任何目录,因此
ls / -als 命令会打印 Linux 文件系统中所有文件(包括大小、权限和隐藏文件)的完整列表。

现在,输出结果只会为 PHP 所知,除非 PHP 脚本开始打印输出,否则用户无法查看这些数据。就像 passthru 函数一样!但优秀的 PHP 开发者知道,除非万不得已,否则绝不使用 passthru 函数。

但是,如果您可以将 ls 命令的输出重定向到 Web 根目录下的一个文件会发生什么?大多数 Web 服务器默认将 Web 根目录设置为 /var/www/,因此您可以将文件存储在那里,稍后下载。您可以一边喝咖啡,一边检查 PHP 安全模式下哪些文件可读,然后简单地使用 cp 命令将这些文件复制到 Web 根目录并下载到您的硬盘上。如果没有文件列表,您只能猜测从哪里复制文件!这比猜测 root 密码还要困难。

因此,如果第一个命令被引用,则由于语法错误,无法附加另一个命令。想想一旦您获得了文件系统中每个文件的完整列表,您可以做多少事情。包括通过 NFS 等方式挂载的设备。安全的第一步是隐藏入口。

另一个可能导致 Web 服务器挂起的命令是:"php "。此命令会持续在整个文件系统上创建文件列表,这不仅会占用硬盘(s) 带宽,还会占用内存和 CPU,因为这些资源需要存储返回的列表。因此,请记住,并非所有来自用户的命令都可以盲目执行。

实际上,永远不要接受来自外部来源的任何命令,只应执行经过验证的内置预定义命令。
trisk at earthling dot net
19 年前
此函数的运行结果与 php.net 示例中显示的结果不一致。

如果您按照建议将编码后的文件名放入双引号中,则它会在文件名中的某些字符(例如&符号)处中断。

例如,如果您有一个名为“foo & bar.jpg”的文件,并且您对此文件使用此函数,则双引号后的结果文件名将变为如下所示,并且找不到该文件。

"foo \& bar.jpg"

如果您需要一个包含空格的单个参数,请不要使用此函数添加双引号,请使用 escapeshellarg(),它会将整个字符串用单引号括起来。

我不理解此特定函数的用途。我看不出它有任何用途,除非您将其传递给另一个函数,并将空格“ ”转换为“\ ”,这将允许您在命令行上直接使用该字符串。
ceejay at trashfactory dot de
18 年前
好吧,伙计们,我发现当打开 safe_mode 时,强制运行 escapeshellargescapeshellcmd 来将命令传递给 execsystempopen 非常困难。

目前,我还没有找到任何可以传递如下命令的解决方案:
cmd -arg1 -arg2 "<BLA varname=\"varvalue\" varname1=\"varvalue1\" />"

问题在于,arg2 的参数是一个看起来像 HTML 标签的字符串,其中设置了各种属性,arg2 中字符串的所有属性都按空格分割。在关闭 safe_mode 的情况下,这种情况不会发生,因此一定是其中一个转义函数导致了功能失效。

为了解决这个问题,我做了一个临时的解决方案,它动态创建了一个脚本文件(通过 fopen),其中只包含带有参数的完整命令,然后执行该脚本文件。我不喜欢这个解决方案,但另一方面,该服务器上的 safe_mode 无法轻松关闭。
abennett at clarku dot edu
18 年前
我有一个 PHP 脚本需要通过 exec 将用户名和密码传递给一个 Perl 脚本。问题是有效的密码字符被转义了……

这是我编写的修复它的 Perl 函数。

sub unescape_string {
my $string = shift;
# 所有这些内插的正则表达式都很慢,因此如果字符串中没有
# 反斜杠,则不要费心处理它
# index() 比正则表达式快
if ( ! index($string,'\\\\') ) {
return $string;
}
my @characters = ('#', '&', ';', '`', '|', '*', '?', '~', '<', '>', '^', '(', ')',
'[', ']', '{', '}', '$', '\\', ',', ' ', '\x0A', '\xFF' );
my $character;
foreach $character (@characters) {
$character = quotemeta($character);
my $pattern = "\\\\(" . $character . ")";
$string =~ s/$pattern/$1/g;
}
return $string;
}

希望这对您有所帮助。
Leon
17 年前
此函数很棒——除非您需要合法地使用转义字符作为命令的一部分。下面的代码保留了用单引号括起来的命令部分,但转义了其余部分,例如:

"echo Never use the '<blink>' tag ; cat /etc/passwd"
变为
"echo Never use the '<blink>' tag \; cat /etc/passwd"
而不是
"echo Never use the '\<blink\>' tag \; cat /etc/passwd"

也就是说,我们确实希望转义“;”,但不想转义 HTML 标签。为了正确安全地运行外部 ImageMagick 的“convert”命令,我确实需要下面的代码……

<?php

// 转义整个字符串
$cmdQ = escapeshellcmd($cmd);

// 构建已引用部分的数组,以及相同的转义部分
preg_match_all('/\'[^\']+\'/', $cmd, $matches);
$matches = current($matches);
$quoted = array();
foreach(
$matches as $match )
$quoted[escapeshellcmd($match)] = $match;

// 将用单引号括起来的段落替换为原始内容
foreach( $quoted as $search => $replace )
$cmdQ = str_replace( $search, $replace, $cmdQ );

return
$cmdQ;

?>
To Top