escapeshellarg 和 escapeshellcmd 有什么区别?

2022-08-30 11:57:31

PHP有2个密切相关的函数,escapeshellarg()escapeshellcmd()。。它们似乎都做了类似的事情,即帮助使字符串更安全地在//etc中使用。system()exec()

我应该使用哪一个?我只是希望能够接受一些用户输入并对其运行命令,而不是让一切都爆炸。如果PHP有一个执行类型函数,它接受一个字符串数组(如argv),绕过shell,我会使用它。类似于Python的subprocess.call()函数。


答案 1

通常,您需要使用 escshellarg,使 shell 命令安全使用单个参数。原因如下:

假设您需要获取目录中的文件列表。您提出了以下建议:

$path  = 'path/to/directory'; // From user input

$files = shell_exec('ls '.$path);
// Executes `ls path/to/directory`

(这是一个不好的方式,但为了说明我)

这对于这条道路来说“很棒”,但假设给出的路径更危险:

$path  = 'path; rm -rf /';

$files = shell_exec('ls '.$path);
// Executes `ls path`, then `rm -rf /`;

由于给定的路径是未经消毒使用的,因此可以运行任何命令。我们可以使用这些方法来尝试防止这种情况。escapeshell*

首先,使用 escapeshellcmd

$path = 'path; rm -rf /';

$files = shell_exec(escapeshellcmd('ls '.$path));
// Executes `ls path\; rm -rf /`;

此方法仅转义可能导致运行多个命令的字符,因此在阻止主要安全风险的同时,仍可能导致传入多个参数。

现在,使用 escapeshellarg

$path = 'path; rm -rf /';

$files = shell_exec('ls '.escapeshellarg($path));
// Executes `ls 'path; rm -rf /'`;

这给了我们我们想要的结果。您会注意到它引用了整个参数,因此不需要转义单个空格等。如果论点本身有引号,它们就会被引用。

总而言之,请确保字符串只有一个命令,同时使字符串可以安全地用作命令的单个参数。escapeshellcmdescapeshellarg


答案 2

确定任何两个发音相似的PHP函数之间的差异的简单解决方案是在PHP中编写一个快速命令行脚本,该脚本输出整个可能的搜索空间并仅显示差异(在本例中,比较256个值):

<?php
    for ($x = 0; $x < 256; $x++)
    {
        if (chr($x) !== escapeshellcmd(chr($x)))  echo $x . " - cmd:  " . chr($x) . " != " . escapeshellcmd(chr($x)) . "\n";
    }

    echo "\n\n";

    for ($x = 0; $x < 256; $x++)
    {
        if (chr($x) !== substr(escapeshellarg(chr($x)), 1, -1))  echo $x . " - arg:  " . chr($x) . " != " . substr(escapeshellarg(chr($x)), 1, -1) . "\n";
    }
?>

在 Windows 命令提示符输出的 PHP 5.6 下运行上述操作:

0 - cmd:    !=
10 - cmd:
 != ^

33 - cmd:  ! != ^!
34 - cmd:  " != ^"
35 - cmd:  # != ^#
36 - cmd:  $ != ^$
37 - cmd:  % != ^%
38 - cmd:  & != ^&
39 - cmd:  ' != ^'
40 - cmd:  ( != ^(
41 - cmd:  ) != ^)
42 - cmd:  * != ^*
59 - cmd:  ; != ^;
60 - cmd:  < != ^<
62 - cmd:  > != ^>
63 - cmd:  ? != ^?
91 - cmd:  [ != ^[
92 - cmd:  \ != ^\
93 - cmd:  ] != ^]
94 - cmd:  ^ != ^^
96 - cmd:  ` != ^`
123 - cmd:  { != ^{
124 - cmd:  | != ^|
125 - cmd:  } != ^}
126 - cmd:  ~ != ^~
255 - cmd:    != ^ 


0 - arg:    !=
33 - arg:  ! !=
34 - arg:  " !=
37 - arg:  % !=
92 - arg:  \ != \\

在 PHP 5.5 下为 Linux 输出运行相同的脚本:

0 - cmd:   !=
10 - cmd:
 != \

34 - cmd:  " != \"
35 - cmd:  # != \#
36 - cmd:  $ != \$
38 - cmd:  & != \&
39 - cmd:  ' != \'
40 - cmd:  ( != \(
41 - cmd:  ) != \)
42 - cmd:  * != \*
59 - cmd:  ; != \;
60 - cmd:  < != \<
62 - cmd:  > != \>
63 - cmd:  ? != \?
91 - cmd:  [ != \[
92 - cmd:  \ != \\
93 - cmd:  ] != \]
94 - cmd:  ^ != \^
96 - cmd:  ` != \`
123 - cmd:  { != \{
124 - cmd:  | != \|
125 - cmd:  } != \}
126 - cmd:  ~ != \~
128 - cmd:   !=
...
255 - cmd:  ÿ !=


0 - arg:   !=
39 - arg:  ' != '\''
128 - arg:   !=
...
255 - arg:  ÿ !=

主要区别在于,Windows下的PHP escapeshellcmd()将字符与插入符号^而不是反斜杠\前缀。Linux 下从 chr(128) 到 chr(255) 的 escapeshellcmd() 和 escapeshellarg() 的奇怪之处可以通过使用无效的 UTF-8 代码点被删除、截断或误解来解释。

同样值得注意的是,escapeshellarg() 转义的字符要少得多,并且仍然可以完成工作。

就整体系统和应用程序的安全性而言,最好使用 escapeshellarg() 并单独转义由用户输入组成的每个参数。

最后一个例子:

echo escapeshellarg("something here") . "\n";
echo escapeshellarg("'something here'") . "\n";
echo escapeshellarg("\"something here\"") . "\n";

窗口输出:

"something here"
"'something here'"
" something here "

Linux 输出:

'something here'
''\''something here'\'''
'"something here"'

Windows上的PHP escapeshellarg()用双引号“字符包围字符串,而Linux使用单引号”字符。Windows上的PHP完全用空格取代了内部双引号(在某些情况下这可能是一个问题)。Linux上的PHP有点不厌其烦地转义单引号,而反斜杠\在Windows上被转义为\\。Windows 上的 PHP escapeshellarg() 也取代了 !和带空格的 % 字符。所有平台都将 \0 替换为空格。

请注意,PHP版本之间的行为不一定一致,PHP文档并不总是反映现实。编写快速脚本或阅读PHP的源代码是弄清楚幕后发生的事情的两种方法。


推荐