【问题标题】:How to stop escapeshellarg() from turning "s into blank spaces on Windows?如何阻止 escapeshellarg() 在 Windows 上将 "s 变成空格?
【发布时间】:2020-11-14 19:04:45
【问题描述】:

PHP 函数escapeshellarg() 变成这样:

"A schema"."A table"

进入这个:

" A schema . A table "

也就是说,它用空格代替引号而不是引用它们。预期和期望的结果(由我)是:

"\"A schema\".\"A table\""

这浪费了我一生中无数个小时的故障排除。在很长一段时间里,我只是不知道发生了什么;我的大脑从来没有想过,实际上完全删除双引号会如此愚蠢!即使这“让它变得安全”,但在逃避它们时这样做是一件很奇怪的事情,不仅是标准和受支持的处理方式,而且不会导致使用此功能产生任何问题。

需要注意的是,我已经阅读了 cmets:https://www.php.net/escapeshellarg

我还阅读了很多其他文章,抱怨这个功能以及 Windows shell 转义等等,所以我至少“模糊地”知道原因:“这很复杂。”

但是,我发现自己被这个核心函数 (escapeshellarg) 彻底削弱了,可悲的是,PHP 经常出现这种情况,我发现自己试图创建自己的函数来替换它。当然,这是非常有问题和可怕的,因为这个功能的一半是与安全相关的!它应该“确保”任意字符串作为命令行参数使用,而不会有攻击者可以“突破”它并执行任意命令的风险!

出于这个原因,我很确定只是这样做:

$likely_not_a_safe_argument = '\"' . str_replace('"', '\"', $user_input) . '\"';

...不安全!如果就这么简单,那么我敢肯定 PHP 开发人员会在 escapeshellarg 中为 Windows 执行此操作。

基本上,我现在不知道该怎么办。是的,我确实需要在参数中发送双引号。下面是一个真实世界的例子:

pg_dump --format plain --verbose --file "dump.txt" --exclude-table-data="\"Test schema\".\"Test table\"" --host="localhost" --port="5432" --username="postgres" --dbname="testdb"

(不,它不需要单引号或其他任何东西。它必须是标识符周围的双引号。)

我应该如何安全地“改进”escapeshellarg() 使其不会破坏我的双引号?其他人都做什么?

【问题讨论】:

  • Cygwin 没有这个问题,也许你可以切换到 Cygwin 或 WSL?
  • @hanshenrik 好吧,我运行的是 Windows 而不是 Unix。 PHP 应该是跨平台的,但现在的普遍态度似乎是“要么你运行 Linux,要么你不能做所有这些事情”。如果 Linux 是一个替代方案,我会在 20 年前开始尝试切换到它时切换。
  • 仅供参考,这里是 escapeshellarg 的最新 source code。它确实用空格字符替换了!%",所以你可能对此无能为力。
  • 由于该函数的源代码非常简单,您可以通过对 " 案例的小修改来重新实现它。
  • @user1597430 嗯...如果是这样,为什么不是他们做的?您似乎认为这是某种“疯狂的边缘情况”,但实际上它完全删除双引号而不是转义它们。像这样重新实现这个与安全相关的功能对我来说听起来不太合适?

标签: php terminal command-line-interface


【解决方案1】:

首先,PHP 的 windows 的 escapeshellarg() 函数不好,事实上它很糟糕,它不能像 ÆØÅ 那样转义完全有效的参数,有时会破坏过程中的输入。您可以通过在此处反转 ReactOS 的 CommandLineToArgvW() 实现来创建正确的 escapeshellarg_windows() 函数:https://github.com/reactos/reactos/blob/10d1afea18db0ef06942239867cbc090e2330b90/dll/win32/shell32/wine/shell32_main.c#L79

不过,我认为这适用于报价,并且实际上同样安全:

function escapeshellarg_shitty_windows_quote_hack(string $arg): string
{
    $hack = bin2hex(random_bytes(11));
    $arg = strtr($arg, [
        '"' => $hack
    ]);
    $arg = escapeshellarg($arg);
    $arg = strtr($arg, [
        $hack => '"""'
    ]);
    return $arg;
}

基本上只是将 " 替换为 """ ,根据 ReactOS 的 CommandLineToArgvW() 代码,这似乎是引号内引号的转义序列

            /* Now count the number of consecutive quotes. Note that qcount
             * already takes into account the opening quote if any, as well as
             * the quote that lead us here.
             */
            while (*s=='"')
            {
                if (++qcount==3)
                {
                    *d++='"';
                    qcount=0;
                }
                s++;
            }

但请注意,这对于 Windows 来说仍然不是一个好的 escapeshellarg 函数。如果确实需要,我可能会尝试在 PHP 中反转 ReactOS CommandLineToArgvW() 函数的效果

(虽然我现在太累了,但即使我不是,如果其他人这样做,我可能会觉得更安全)

【讨论】:

  • 我觉得这样说很粗鲁,但我真的对这个答案一无所知。
  • @user13908603 ELI5:用于 windows 的 php 的 escapeshellarg() 函数构建得很糟糕。他们在制作该功能的 Windows 版本时做得不好。要快速修复引号问题,请改用 escapeshellarg_shitty_windows_quote_hack() 函数。但这只是引号的快速修复,它不是 Windows 上 php 的 escapeshellarg 函数的正确修复,有人应该从头开始重写该函数。
  • 您的解决方案有潜在危险。
  • @user1597430 对我来说看起来不错,escapeshellarg() 将 everything 放在引号内,""" 是 Windows 上 quotes inside quotes 的转义序列,我看不到如何使用它来转义引号并运行任意命令 =/ 顺便说一句,尝试在 windows cmd 上运行它:php -r "var_dump($argv);" """"this is surrounded by quotes"""" - 正如预期的那样,您会得到 "this is surrounded by quotes"(引号作为字符串的一部分)
【解决方案2】:

你可以使用addlashes();函数。

https://www.php.net/manual/de/function.addslashes.php

【讨论】:

  • 但这安全吗?如果是,为什么不使用 escapeshellarg() 呢?!
  • 是的,应该是安全的。像 escapeshellarg() 这样的接缝只是删除了所有的引号。
  • @mrfr34k 这不是真的,它的作用不止于此。例如,它...去除非 ascii 多字节字符,如 ÆØÅ★ツ,另外它用空格替换双引号 (") 和百分号 (%) 和感叹号 (!),另外它转义 \ 与另一个 \
猜你喜欢
  • 1970-01-01
  • 2014-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多