What's the difference between escapeshellarg and escapeshellcmd?

35,495

Solution 1

From http://ie2.php.net/manual/en/function.escapeshellarg.php

escapeshellarg() adds single quotes around a string and quotes/escapes any existing single quotes allowing you to pass a string directly to a shell function and having it be treated as a single safe argument.

escapeshellarg, as its name indicates, is used as passing shell argument(s). For example, you want to list current directory,

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

Both do similar things and simply depends on how you handle your logic, do make sure your normalize and validate your input before passing directly to these methods for better security.

Solution 2

Generally, you'll want to use escapeshellarg, making a single argument to a shell command safe. Here's why:

Suppose you need to get a list of files in a directory. You come up with the following:

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

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

(This is a bad way of doing this, but for illustration bear with me)

This works "great" for this path, but suppose the path given was something more dangerous:

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

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

Because the path given was used unsanitised, any command can potentially be run. We can use the escapeshell* methods to try to prevent this.

First, using escapeshellcmd:

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

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

This method only escapes characters that could lead to running multiple commands, so while it stops the major security risk, it can still lead to multiple parameters being passed in.

Now, using escapeshellarg:

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

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

That gives us the result we want. You'll notice it's quoted the entire argument, so individual spaces, etc, do not need to be escaped. If the argument were to have quotes itself, they would be quoted.

To summarise, escapeshellcmd makes sure a string is only one command, while escapeshellarg makes a string safe to use as a single argument to a command.

Solution 3

A simple solution to determine the difference between any two similar-sounding PHP functions is to write a quick command-line script in PHP that outputs the entire possible search space and just show differences (in this case, comparing 256 values):

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

Running the above under PHP 5.6 on the Windows Command Prompt outputs:

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:  \ != \\

Running the same script under PHP 5.5 for Linux outputs:

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:  ÿ !=

The main difference is that PHP escapeshellcmd() under Windows prefixes characters with a caret ^ instead of a backslash \. The oddities under Linux from chr(128) through chr(255) for both escapeshellcmd() and escapeshellarg() can be explained by the use of invalid UTF-8 code points being dropped, truncated, or misinterpreted.

Also of note is that escapeshellarg() escapes far fewer characters and still gets the job done.

In terms of overall system and application safety and security, you are better off using escapeshellarg() and individually escape each argument that consists of user input.

One final example:

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

Windows outputs:

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

Linux outputs:

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

PHP escapeshellarg() on Windows surrounds the string with the double-quote " character while Linux uses the single-quote ' character. PHP on Windows completely replaces internal double-quotes with spaces (which could be a problem in some cases). PHP on Linux goes a bit out of its way to escape single-quotes and backslashes \ are escaped \\ on Windows. PHP escapeshellarg() on Windows also replaces ! and % characters with spaces. All platforms replace \0 with spaces.

Note that behavior is not necessarily consistent between PHP versions and the PHP documentation doesn't always reflect reality. Writing a quick script or reading the source code of PHP are two ways to figure out what's happening behind the scenes.

Solution 4

The PHP docs spell out the difference:

escapeshellcmd:

Following characters are preceded by a backslash: #&;`|*?~<>^()[]{}$\, \x0A and \xFF. ' and " are escaped only if they are not paired. In Windows, all these characters plus % are replaced by a space instead.

escapeshellarg:

adds single quotes around a string and quotes/escapes any existing single quotes allowing you to pass a string directly to a shell function and having it be treated as a single safe argument.

Source:

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

Share:
35,495
Amandasaurus
Author by

Amandasaurus

I'm a Linux user

Updated on July 09, 2022

Comments

  • Amandasaurus
    Amandasaurus almost 2 years

    PHP has 2 closely related functions, escapeshellarg() and escapeshellcmd(). They both seem to do similar things, namely help make a string safer to use in system()/exec()/etc.

    Which one should I use? I just want to be able to take some user input and run a command on it, and not have everything blow up. If PHP had an exec-type-function that took an array of strings (like argv), which bypasses the shell, I'd use that. Similar to Python's subprocess.call() function.

  • cyphunk
    cyphunk almost 12 years
    This is not an answer. The question was what are the differences.
  • Li-chih Wu
    Li-chih Wu over 11 years
    The real question is "Which one should I use?". I think Jay answered that question.
  • BenMorel
    BenMorel almost 6 years
    I wouldn't say that they do "similar things", check Adam's answer for the real difference.
  • Sadam
    Sadam over 2 years
    can you please edit question and post output of both commands so i can understand easily Executes `ls path\; rm -rf /`; output of this and this Executes `ls path\; rm -rf /`;
  • Adam
    Adam over 2 years
    @Sadam The output of those commands isn't relevant; what's important is one runs ls with four arguments path\;, rm, -rf & /, while the other runs ls with one argument path; rm -rf /. Pay special attention to the quotes within each example command in the answer.