2

The PHP function escapeshellarg() turns this:

"A schema"."A table"

Into this:

" A schema . A table "

That is, it replaces the quotes with spaces instead of quoting them. The expected and desired result (by me) is:

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

This has wasted countless hours of my life troubleshooting. For the longest time, I just couldn't figure out what was happening; it never occured to my brain that it could be so stupid as to actually remove the double quotes entirely! Even if this "makes it safe", it's such a bizarre thing to do when escaping them would not only be the standard and supported way of handling this, but would result in no problems arising from using this function.

It should be noted that I've read the comments on: https://www.php.net/escapeshellarg

I have also read a lot of other texts complaining about this function and about Windows shell escaping and whatnot, so I'm at least "vaguely" aware of the reason: "It's complicated."

However, I find myself utterly crippled by this core function (escapeshellarg), as sadly so often is the case with PHP, and I find myself trying to make my own function to replace it. This, of course, is extremely problematic and scary since half the point of this function is security-related! It's supposed to "make safe" an arbitrary string to be used as an argument on the command line, without risking that an attacker can "break out" from it and execute arbitrary commands!

For this reason, I'm pretty sure that simply doing:

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

... is NOT safe! If it were that simple, then I'm certain that the PHP developers would do that for Windows inside escapeshellarg.

Basically, I have no idea what to do now. And yes, I do need to send double quotes in arguments. Here is a real-world example of where it's necessary:

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

(No, it doesn't take single quotes or anything else. It must be double quotes around the identifiers.)

How should I safely "improve" escapeshellarg() so that it doesn't nuke my double quotes? What does everyone else do?

  • Cygwin doesn't have that problem, maybe you could switch to Cygwin or WSL? – hanshenrik Jul 24 '20 at 21:48
  • @hanshenrik Well, I'm running Windows and not Unix. PHP is supposed to be cross-platform but the general attitude these days seems to be that "either you run Linux or you can't do all these things". If Linux were an alternative, I'd have switched 20 years ago when I first begun trying to switch to it. – user13908603 Jul 24 '20 at 21:58
  • Just for your info, here's the latest [source code](https://github.com/php/php-src/blob/91fbd12d5736b3cc9fc6bc2545e877dd65be1f6c/ext/standard/exec.c#L423) of escapeshellarg. It does replace `!`, `%` and `"` with a space char, so you probably can do nothing about that. – user1597430 Jul 24 '20 at 22:11
  • And since the source code of that function is pretty simple, you can reimplement it with a small modification for your `"` case. – user1597430 Jul 24 '20 at 22:16
  • @user1597430 Well... If that's the case, how come this isn't done by them? You seem to think that this is some kind of "crazy edge-case", but it literally *removes* double quotes entirely instead of escaping them. It doesn't sound right for me to re-implement this security-related function like this? – user13908603 Jul 24 '20 at 22:20
  • @user13908603, Win OS + not really popular OS-dependent function = "well-known" bugs or bad solutions. You can ask for a bugfix via PHP bug tracker, github or directly the maintainer (all logical parts of the code divided by the several devs). You can also offer your own solution if you do understand C lang. – user1597430 Jul 24 '20 at 22:29
  • [This may come in handy.](https://github.com/johnstevenson/winbox-args) – Jeto Jul 24 '20 at 22:33
  • first off, PHP's windows' escapeshellarg() function is not good, in fact it's bad, it fails to escape perfectly valid arguments like `ÆØÅ`, sometimes corrupting the input in the process. you could probably create a proper escapeshellarg_windows() function by reversing ReactOS's CommandLineToArgvW()-implementation here: https://github.com/reactos/reactos/blob/10d1afea18db0ef06942239867cbc090e2330b90/dll/win32/shell32/wine/shell32_main.c#L79 – hanshenrik Jul 25 '20 at 00:10

2 Answers2

0

you could use the addslashes(); function.

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

mrfr34k
  • 65
  • 9
  • But is that safe? And if it is, why is not escapeshellarg() doing that?! – user13908603 Jul 24 '20 at 21:32
  • Yes sould be safe. Seams like escapeshellarg() is just removing all the quotes. – mrfr34k Jul 24 '20 at 21:37
  • @mrfr34k that's not true, it does more than that. for example, it... strips non-ascii multibyte characters like ÆØÅ★ツ , additionally it replaces double quotes (`"`) and percent-sign (`%`) and exclamation marks (`!`) with spaces, additionally it escapes \ with another \ – hanshenrik Jul 24 '20 at 23:29
0

first off, PHP's windows' escapeshellarg() function is not good, in fact it's bad, it fails to escape perfectly valid arguments like ÆØÅ, sometimes corrupting the input in the process. you could probably create a proper escapeshellarg_windows() function by reversing ReactOS's CommandLineToArgvW()-implementation here: https://github.com/reactos/reactos/blob/10d1afea18db0ef06942239867cbc090e2330b90/dll/win32/shell32/wine/shell32_main.c#L79

still, i think this would work for quotes, and be practically equally safe:

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;
}

basically just replacing " with """ , which seems to be an escape sequence for quotes inside quotes, per ReactOS's, CommandLineToArgvW() code

            /* 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++;
            }

but note that this is still not a good escapeshellarg function for windows. if that's actually needed, i would probably try to reverse the effects of ReactOS CommandLineToArgvW() function, in PHP

(i'm far too tired to attempt that now though, but even if i wasn't, i'd probably feel safer if som1 else did it)

hanshenrik
  • 15,263
  • 3
  • 28
  • 61
  • I feel rude for saying this, but I truly don't understand anything about this answer. – user13908603 Jul 25 '20 at 03:20
  • @user13908603 ELI5: php's escapeshellarg() function for windows is badly built. they did a bad job when they made the windows-version of the function. for a quickfix to the quotes-problem, use the ```escapeshellarg_shitty_windows_quote_hack()``` function instead. but that is just a quickfix for quotes, it's not a proper fix for php's escapeshellarg function on windows, someone should re-write that function from scratch. – hanshenrik Jul 25 '20 at 08:25
  • Your solution is potentially dangerous. – user1597430 Jul 25 '20 at 18:58
  • @user1597430 looks ok to me, escapeshellarg() puts *everything* inside quotes, and `"""` is an escape sequence for `quotes inside quotes` on windows, i don't see how that can be used to escape the quotes and run arbitrary commands =/ btw try running this on windows cmd: ```php -r "var_dump($argv);" """"this is surrounded by quotes""""``` - as expected you get `"this is surrounded by quotes"` (with the quotes as part of the string) – hanshenrik Jul 25 '20 at 19:13