escapeshellarg

(PHP 4 >= 4.0.3, PHP 5, PHP 7, PHP 8)

escapeshellarg转义字符串以用作 shell 参数

描述

escapeshellarg(string $arg): string

escapeshellarg() 在字符串周围添加单引号,并对任何现有的单引号进行引用/转义,允许您将字符串直接传递给 shell 函数,并将其视为单个安全参数。此函数应用于转义来自用户输入的 shell 函数的单个参数。shell 函数包括 exec()system()反引号运算符

在 Windows 上,escapeshellarg() 而是用空格替换百分号、感叹号(延迟变量替换)和双引号,并在字符串周围添加双引号。此外,每个连续的反斜杠 (\) 都会被一个额外的反斜杠转义。

参数

arg

要转义的参数。

返回值

转义后的字符串。

示例

示例 #1 escapeshellarg() 示例

<?php
system
('ls '.escapeshellarg($dir));
?>

参见

添加备注

用户贡献的备注 19 个备注

100
phil at philfreo dot com
14 年前
当 escapeshellarg() 从 UTF-8 字符串中剥离我的非 ASCII 字符时,添加以下内容修复了问题

<?php
setlocale
(LC_CTYPE, "en_US.UTF-8");
?>
6
lucgommans.nl
1 年前
这并不能防止所有形式的命令注入。

<?php
// GET /example.php?file[]=x&file[]=-I&file[]=bash%20-c%20touch\%20/tmp/lucwashere

$files_to_archive = [];
foreach (
$_GET['file'] as $file) {
$files_to_archive[] = escapeshellarg($file);
}

exec("tar cf my.tar " . implode(' ', $files_to_archive));
?>

尽管正确地转义以防止 shell 注入,但这将运行指定的代码。这些参数指示 tar 在 bash 中运行命令。然后,您可以检查 /tmp 目录以验证代码是否已运行。

当然,攻击者会用更恶意的变体替换它。对于黑盒漏洞测试,可以安全地使用几秒钟的睡眠来确定参数是否易受攻击。

此处的漏洞在于,tar 与几乎所有其他程序一样,会将以连字符开头的参数解释为修改其行为的选项。许多程序,如 tcpdump、man、zip、gpg、tar 等,都有一个选项可以执行另一个命令。即使您使用没有(并且永远不会)有这种执行选项的程序,它的功能也会受到指定额外选项的影响,无论是故意还是偶然,因为某些字符串碰巧以连字符开头(类似于如何易受 SQL 注入攻击的字段会在任何包含单引号或引号符号的数据上意外断开:这仅仅是糟糕的用户体验)。

许多程序允许使用双连字符,--,将位置参数与选项分开。如果您将上面的代码更改为使用此 exec 行,它将不再易受攻击

<?php
// 注意 -- 在指定我们想要的选项后
exec("tar cf my.tar -- " . implode(' ', $files_to_archive));
?>

并非所有程序都支持此分隔符,在这种情况下,您需要找到替代的输入方法(例如,nmap -iL targets.txt 而不是 nmap 2001:db8::/96)或拒绝以连字符开头的参数。

当然,理想情况下,人们会使用通过库进行数据绑定来避免完全进行危险的转义操作,类似于参数化的 SQL 查询,但我还没有看到提及此注意事项,并且认为它值得添加,以备仍使用 escapeshellarg() 的情况。
13
daverandom
5 年前
在 Windows 上,此函数天真地剥离特殊字符并用空格替换它们。生成的字符串始终可安全地用于 exec() 等,但操作不是无损的 - 包含 " 或 % 的字符串将不会正确地传递给子进程。

在 Windows 上正确转义 shell 命令不是一件简单的事情。程序必须考虑两种不同的转义机制,它们具有不同的用途
1) CommandLineToArgV() Windows 系统函数使用的约定,子进程使用它来解释命令行字符串
2) cmd.exe 用于转义 shell 元字符(例如,输出重定向控制)的约定

所有命令都应针对 CommandLineToArgV() 进行转义 - 此机制在将每个参数附加到命令行字符串之前分别应用于每个参数。生成的字符串可以安全地与 CreateProcess() 函数族一起使用。然而...

在几乎所有从 PHP 在 Windows 上创建子进程的情况下,都是通过间接调用 cmd.exe 来完成的 - 这是为了能够使用 shell 功能,如 I/O 重定向和环境变量替换。因此,整个命令字符串必须针对 cmd.exe 进一步转义。如果执行的命令包含通过 cmd.exe 的进一步间接调用,则每个子命令必须针对每个间接级别再次转义。

以下函数可用于正确地转义字符串,以便它们安全地传递给子进程

<?php

/**
* 按照 CommandLineToArgV() 规范转义单个值
* https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85)
*/
function escape_win32_argv(string $value): string
{
static
$expr = '(
[\x00-\x20\x7F"] # 控制字符、空格或双引号
| \\\\++ (?=("|$)) # 反斜杠后跟引号或位于末尾
)ux'
;

if (
$value === '') {
return
'""';
}

$quote = false;
$replacer = function($match) use($value, &$quote) {
switch (
$match[0][0]) { // 只检查匹配的第一个字节

case '"': // 双引号被转义,必须加引号
$match[0] = '\\"';
case
' ': case "\t": // 空格和制表符可以,但必须加引号
$quote = true;
return
$match[0];

case
'\\': // 如果加引号,匹配的反斜杠会被转义
return $match[0] . $match[0];

default: throw new
InvalidArgumentException(sprintf(
"无效字节,偏移量为 %d:0x%02X",
strpos($value, $match[0]), ord($match[0])
));
}
};

$escaped = preg_replace_callback($expr, $replacer, (string)$value);

if (
$escaped === null) {
throw
preg_last_error() === PREG_BAD_UTF8_ERROR
? new InvalidArgumentException("无效的 UTF-8 字符串")
: new
Error("PCRE 错误: " . preg_last_error());
}

return
$quote // 仅在需要时加引号
? '"' . $escaped . '"'
: $value;
}

/** 使用 ^ 转义 cmd.exe 元字符 */
function escape_win32_cmd(string $value): string
{
return
preg_replace('([()%!^"<>&|])', '^$0', $value);
}

/** 像 shell_exec() 一样,但绕过 cmd.exe */
function noshell_exec(string $command): string
{
static
$descriptors = [['pipe', 'r'],['pipe', 'w'],['pipe', 'w']],
$options = ['bypass_shell' => true];

if (!
$proc = proc_open($command, $descriptors, $pipes, null, null, $options)) {
throw new
\Error('创建子进程失败');
}

fclose($pipes[0]);
$result = stream_get_contents($pipes[1]);
fclose($pipes[1]);
stream_get_contents($pipes[2]);
fclose($pipes[2]);
proc_close($proc);

return
$result;
}

// 用法

$badString = '包含 "C:\\quotes\\" 或恶意 %OS% 东西 \\ 的字符串';
$cmdParts = [
'php',
'-d', 'display_errors=1', '-d', 'error_reporting=-1',
'-r', 'echo $argv[1];',
$badString // 子进程 $argv[1] 值
];

/* 典型方法 - 在 POSIX shell 上正常工作,但在 Windows 上完全错误
*/
$wrong = implode(' ', array_map('escapeshellarg', $cmdParts));

/* 始终单独转义每个参数 */
$escaped = implode(' ', array_map('escape_win32_argv', $cmdParts));

/* 在几乎所有情况下,也为 cmd.exe 转义 - 唯一的例外是
使用 proc_open() 并且使用 bypass_shell 选项。cmd 不会单独处理
参数,因此可以转义整个命令行字符串,
无需单独处理参数 */
$cmd = escape_win32_cmd($escaped);

$cmds = [
'escapeshellarg() - 错误' => $wrong,
'escape_win32_argv() - 适用于 bypass_shell' => $escaped,
'escape_win32_cmd(escape_win32_argv()) - 适用于其他所有情况' => $cmd,
];

function
check($original, $received)
{
$match = $original === $received ? '=' : 'X';
return
"$match '$received'";
}

foreach (
$cmds as $description => $cmd) {
echo
"$description\n";
echo
" $cmd\n";
echo
" 原文: '$badString'\n";
echo
" shell_exec(): " . check($badString, shell_exec($cmd)) . "\n";
echo
" noshell_exec(): " . check($badString, noshell_exec($cmd)) . "\n";
echo
"\n";
}
21
匿名
18 年前
上面大多数评论误解了此函数。它不需要转义诸如 '$' 和 '`' 之类的字符 - 它利用了这样一个事实,即 shell 在单引号内不会将任何字符视为特殊字符(单引号字符本身除外)。使用此函数的正确方法是在一个变量上调用它,该变量旨在作为单个参数传递给命令行程序 - 你不能在整个命令行上调用它。

上面评论说此函数在输入为空字符串时行为很差的人是正确的 - 这是一个错误。在这种情况下,它确实应该返回两个单引号。
15
egorinsk at gmail dot com
17 年前
在 Windows 下,此函数将字符串放入双引号,而不是单引号,并将 %(百分号)替换为空格,这就是为什么不可能通过此函数传递名称中包含百分号的文件名。
3
Jasen
5 年前
如果你想要对空输入使用空参数

使用以下形式

escapeshellarg($input)."''"

shell 将把 foo'' 视为 foo,但空输入将变为空参数,而不是缺少参数。
7
phpman at crustynet dot org dot uk
14 年前
来自“rmays at castlecomm dot com”的评论是错误的:在构建 shell 参数时,单引号在单引号字符串中无法使用反斜杠转义。此函数的输出实际上是正确的。它退出单引号字符串,包含一个带反斜杠转义的文字单引号,然后继续单引号字符串。观察

[shellarg.php]
<?php

system
("echo ' single quote\'d '");
system("echo ' single quote'\''d '");
?>

$ php shellarg.php
sh: -c: line 0: unexpected EOF while looking for matching `''
sh: -c: line 1: syntax error: unexpected end of file
single quote'd
9
Audun
16 年前
在 Windows 上,% 被替换为空格的原因是,在 cmd.exe 中无法转义或引用它们,以防止环境变量扩展。例如,如果 %path% 在您的参数中,它将始终被扩展,因此唯一安全的操作是将 % 替换为其他内容。

或者,您可以在调用 exec() 之前清除环境,但这会产生副作用。
1
Jannis
2 年前
escapeshellarg() 将根据您的区域设置删除所有无效字符(例如,当区域设置/LC_CTYPE 为 UTF-8 时,拉丁-1 字符将被删除)。

请记住,区域设置支持取决于您编译时使用的 C 标准库。这可能会导致在使用具有较差区域设置支持的标准库(而非 glibc)的嵌入式系统上出现奇怪的行为。
3
info at infosoporte dot com
15 年前
如果 escapeshellarg() 函数从给定字符串中删除了您的重音符号(例如带“重音”的 á),请确保您的 LC_ALL 变量正确。如果通过网络使用它,您需要在通过您的 shell 设置 LC_ALL 后(例如 export LC_ALL=es_ES.utf8),重新启动 Apache 或相应的 Web 服务器。
7
sblyons+php at gmail dot com
12 年前
如果在序列化对象上使用 escapeshellarg(),请注意。序列化对象包含空字节,escapeshellarg 在第一个空字节处停止,因此您将不会收到完整的参数。(我认为这是一个错误,虽然不确定在这种情况下应该怎么做。也许序列化不应该使用空字节,但这现在已经太晚了)。
我发现的解决方法是在命令行上传递序列化对象时先使用 base64_encode() 编码,然后在另一端解码。
0
Tony C
1 年前
在使用此函数将文件名传递给命令行时,我注意到 shell_exec() 失败了。经过进一步调查,发现 escapeshellarg() 从文件名中删除了双空格。

例如

$filename = "my super file.txt"; // 注意 "my" 后面的双空格

echo escapeshellarg($filename);

产生
'my super file.txt'

(第二个空格被删除了)
0
crose
4 年前
Ubuntu:想知道为什么您的系统区域设置(例如“en_US.UTF-8”)没有继承到您的 Apache(仍然是“C”)?

检查`/etc/apache2/envvars`... 激活行`. /etc/default/locale`
-1
divinity76 at gmail dot com
3 年前
如果您需要即使在 Windows 上运行时也要生成 Linux 参数,请尝试

<?php

/**
* 使用 linux 转义规则引用参数,无论主机操作系统如何
*(例如,即使在 Windows 上运行,它也会使用 linux 转义规则)
*
* @param string $arg
* @throws \InvalidArgumentException if argument contains null bytes
* @return string
*/
/*public static*/
function linux_escapeshellarg(string $arg): string
{
if (
false !== strpos($arg, "\x00")) {
throw new
\InvalidArgumentException("argument contains null bytes, it's impossible to escape null bytes!");
}
return
"'" . strtr($arg, [
"'" => "'\\''"
]) . "'";
}
-1
Anonymous
3 年前
如果您需要即使在 Windows 上运行时也要生成 Linux 参数,请尝试

<?php

/**
* 使用 linux 转义规则引用参数,无论主机操作系统如何
*(例如,即使在 Windows 上运行,它也会使用 linux 转义规则)
*
* @param string $arg
* @throws \InvalidArgumentException if argument contains null bytes
* @return string
*/
/*public static*/
function linux_escapeshellarg(string $arg): string
{
if (
false !== strpos($arg, "\x00")) {
throw new
\InvalidArgumentException("argument contains null bytes, it's impossible to escape null bytes!");
}
return
"'" . strtr($arg, [
"'" => "'\\''"
]) . "'";
}
0
jon at wroth dot org
10 年前
我为 Windows 想出的 escapeshellarg() 的最佳替代方案是这个
<?php
function w32escapeshellarg($s)
{ return
'"' . addcslashes($s, '\\"') . '"'; }
?>
0
jrbeaure at uvm dot edu
15 年前
当通过此命令将包含连字符的 LaTeX 代码字符串作为参数传递给 pdflatex 时,它将使用此命令进行转义,这会导致失败。
-1
wijnand at jpresult dot nl
11 年前
如果您需要处理特殊字符,这里有一个快速简便的替换此函数的方法。

<?php
/**
* 一个丑陋的、不安全的非 ASCII 字符 escapeshellarg() 替换。
*/
function escapeshellarg_special($file) {
return
"'" . str_replace("'", "'\"'\"'", $file) . "'";
}
?>
-4
vosechu at roman-fleuve dot com
20 年前
如果 escapeshellarg() 在空输入上返回了一些内容,它可能会破坏比它帮助的更多程序。即使是两个“'”或两个“''”,此函数也不会像它应该的那样工作(即,不返回任何内容)。

但是,大多数人不会在他们的命令中输入“”,但我可以看到它在某些情况下可能很有用。
也许命令中可以有一个选项,用于返回我们想要的空类型。我可能希望返回空字符,其他人可能希望返回“”,而其他人可能根本不希望返回任何内容。
To Top