escapeshellarg 和 escapeshellcmd 有啥区别?

Posted

技术标签:

【中文标题】escapeshellarg 和 escapeshellcmd 有啥区别?【英文标题】:What's the difference between escapeshellarg and escapeshellcmd?escapeshellarg 和 escapeshellcmd 有什么区别? 【发布时间】:2010-12-25 07:01:16 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

确定任何两个听起来相似的 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";

Windows 输出:

"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 源代码是了解幕后发生的事情的两种方法。

【讨论】:

【参考方案2】:

通常,您会希望使用escapeshellarg,从而使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 /'`;

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

总而言之,escapeshellcmd 确保字符串只是一个命令,而 escapeshellarg 确保字符串安全地用作命令的单个参数。

【讨论】:

您能否编辑问题并发布这两个命令的输出,以便我可以轻松理解Executes `ls path\; rm -rf /`; 这个和这个Executes `ls path\; rm -rf /`; 的输出【参考方案3】:

PHP 文档说明了区别:

escapeshellcmd

后面的字符以反斜杠开头:#&;`|*?~^()[]$\, \x0A 和 \xFF。 ' 和 " 仅在它们未配对时才被转义。在 Windows,所有这些字符加上 % 都被替换为空格。

escapeshellarg

在字符串周围添加单引号并引用/转义任何现有的 单引号允许您将字符串直接传递给 shell 函数并将其视为单个安全参数。

来源:

http://www.php.net/manual/en/function.escapeshellcmd.php http://www.php.net/manual/en/function.escapeshellarg.php

【讨论】:

【参考方案4】:

来自http://ie2.php.net/manual/en/function.escapeshellarg.php

escapeshellarg() 添加单引号 围绕一个字符串和引号/转义任何 现有的单引号允许您 将字符串直接传递给 shell 功能并将其视为 单个安全参数。

escapeshellarg,顾名思义,用作传递 shell 参数。比如你想列出当前目录,

$dir = ".";
system('ls '.escapeshellarg($dir));
escapeshellcmd('ls $dir');

两者都做类似的事情,只是取决于您如何处理逻辑,请确保在直接传递给这些方法之前对输入进行规范化和验证,以提高安全性。

【讨论】:

这不是答案。问题是有什么区别。 真正的问题是“我应该使用哪一个?”。我认为杰回答了这个问题。 我不会说他们做“类似的事情”,请查看亚当的答案以了解真正的区别。

以上是关于escapeshellarg 和 escapeshellcmd 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

escapeshellarg与escapeshellcmd共伤

出于安全原因,Composer escapeshellarg() 已被禁用

雷林鹏分享:CodeIgniter文件上传错误:escapeshellarg() has been disabled for security reasons

如何在 Windows 上使用 escapeshellarg() 但“针对 Linux”(反之亦然)?

[BUUCTF 2018]Online Tool

[BUUCTF 2018]Online Tool