PHP exec() with double quote argument - php

I've been having trouble running a command using PHP's exec() function on Windows. Per a comment on PHP's site on exec():
In Windows, exec() issues an internal call to "cmd /c your_command".
My command looks like:
"path\to\program.exe" -flag1 attribute1 -flag2 attribute2 -flag3 "attribute3 attribute4"
Under regular execution of this command in my local command prompt, without the /c flag, the command runs fine. However, with the introduction of the /c flag, command prompt tells me that "The system cannot find the path specified."
I think the command prompt is interpreting the double-quoted argument as a path to another file, but that's the furthest I've gotten with this problem.
Does anybody have any ideas on how to get past this? Thanks!

I also encountered this issue, and the cause of it is, indeed, the internal use of "cmd /c" as described in your own answer.
I have done some investigation, and have found that this was resolved in PHP 5.3, which is why some commenters were unable to reproduce it.
It was fixed in the following commit:
https://github.com/php/php-src/commit/19322fc782af429938da8d3b421c0807cf1b848a#diff-15f2d3ef68f383a87cce668765721041R221
For anyone who still needs to support PHP 5.2, it is fairly easy to replicate the fix in your own code. Instead of:
$cmd = "...any shell command, maybe with multiple quotes...";
exec($cmd);
use
function safe_exec($cmd, &$output = null, &$result_code = null) {
if (strtoupper(substr(php_uname('s'), 0, 3)) == "WIN"
&& version_compare(PHP_VERSION, "5.3", "<"))
{
$cmd = '"' . $cmd . '"';
}
return exec($cmd, $output, $result_code);
}
$cmd = "...any shell command, maybe with multiple quotes...";
safe_exec($cmd);
This replicates the behaviour of PHP 5.3 and above, in the same way as in the above-linked commit.
Note that this applies to all shell commands, not just exec() as used in my example.

I've figured out the answer by myself...
After perusing cmd.exe's /? and trying to decipher that, I've noticed one important section:
If all of the following conditions are met, then quote characters on the command line are preserved:
No /S switch (Strip quotes)
Exactly two quote characters
No special characters between the two quote characters, where special is one of: & < >( ) # ^ |
There are one or more whitespace characters between the the two quote characters
The string between the two quote characters is the name of an executable file.
Otherwise, old behavior is to see if the first character is a quote character and if so, strip the leading character and remove the last quote character on the command line, preserving any text after the last quote character. To negate this behaviour use a double set of quotes "" at the start and end of the command line.
It seems as though if there more than one pair of quotes at any time, quotation marks will be stripped from the second pair of quotes and on.
A relevant question: How do I deal with quote characters when using cmd.exe but not completely relevant, since PHP will not allow you to modify its exec() command by putting an /S flag on its call (which would definitely be a lot easier).
I've managed to work around this problem by directly changing directories with chdir() to the folder where the .exe is located, then chdir()'ing back to my original working directory. It's an extremely hacky solution, given the case that I'm lucky that I only have one set of arguments using double quotes. If anybody were to have multiple arguments using double quotes, I wouldn't know how to solve that...

Just a guess (I am not familiar with PHP on windows): maybe escape the quotes as " becoming ""?
"path\to\program.exe" -flag1 attribute1 -flag2 attribute2 -flag3 ""attribute3 attribute4""
Whatever the solution is, make sure that when there's some form of user-input that gets passed to this command as arguments that you use escapeshellarg and/or escapeshellcmd.

I hope it will help
escapeshellarg() — Escape a string to be used as a shell argument
escapeshellcmd() — Escape shell metacharacters

Related

why is curl waiting for a keypress?

I simply want to run the following command:
curl https://jkanime.net/um.php?e=VTJpeCsrL3BVY2xMaEd0YWhyM1k4SDdHelZ4OGZSeXFsOHBla1QrcnBPQm4wUWc1eE1TOThmWlBOb2xLOEJCeWlGenpML2tYelA3Tm8xU1lDMDRwUlE9PTo616MlXtdmRfi6FOwaoBRqeA--&t=5ec9cff996b02bf751b55c92c4cb1170
It seems to connect just fine, but it hangs waiting for a keypress. How can I automatize this in a script?
EDIT:
I have absolutely no idea but it solved by erasing the -- part of the URL. Can someone explain to me why?
In bash, you must use quotes (single or double, that depends what you want to do) to build one complete string by escaping some characters : ` $ ( ) [ ] { } & | space
So, use quotes around the URL:
curl --url 'https://jkanime.net/um.php?e=VTJpeCsrL3BVY2xMaEd0YWhyM1k4SDdHelZ4OGZSeXFsOHBla1QrcnBPQm4wUWc1eE1TOThmWlBOb2xLOEJCeWlGenpML2tYelA3Tm8xU1lDMDRwUlE9PTo616MlXtdmRfi6FOwaoBRqeA--&t=5ec9cff996b02bf751b55c92c4cb1170'
You don't describe your environment, but it's probably your shell interpreting the unquoted hyphens. In bash, double hyphens terminate processing. If you surround your URL with quotes, it will avoid this behavior and pass the quoted string to curl.

Find position of console escape code in string

tl;dr How do I write strpos($haystack, '^[[H^[[2J') in PHP?
A linux command line app delivers a screen-full of data followed by a console escape code to return the cursor to the "home" position in a regular loop. To access the data I piped the output to my own script like so:
$ ./otherapp | php myscript.php
It's a continuous stream of data so I use this example for non-blocking stream consumption.
Now to decode the output I need to grab a full frame/screen full of data. The escape codes are shown in nano as ^[[H and ^[[2J. The easiest way seems to be detecting these escape codes and using the output between them.
How are the escape codes represented in a PHP string? Can I use strpos (or mbstring equivalent) to detect their position?
It seems you're on a Unix environment. To achieve what your escape codes do, you can try with echo -en "\033[H" and echo -en "\033[2J", so I guess you should try something like strpos('\033[H\033[2J', $haystack).
Maybe you'll need to add an extra backslash ('\\\033[H\\\033{2J').
Since \033 means "the byte with octal value 33", which is ESC, this should work:
strpos("\e[H\e[2J", $haystack)
You need to enclose it in double-quotes (") if you want PHP to interpret escape sequences for special characters.

PHP SSH Lib, echoing to a file

I'm trying to create a PHP script that creates a file on a remote linux server through ssh, and echos the file contents into it.
However, I cannot figure out how to correctly and safely encode/escape the file contents, so that the contents don't get interpreted as commands.
I'm using phpseclib from here.
I've tried something like
echo $ssh->exec('sudo echo "' . escapeshellarg($newConfig) . '" > /etc/nginx/nginx.conf') . "\n";
but without success.
Thanks,
Steve
What about escapeshellcmd? Quoting the description of that vs escapeshellarg:
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.
...and...
escapeshellcmd() escapes any characters in a string that might be used
to trick a shell command into executing arbitrary commands. This
function should be used to make sure that any data coming from user
input is escaped before this data is passed to the exec() or system()
functions, or to the backtick operator.
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.
I was going about this all wrong, I should have used Net_SFTP instead of NET_SSH for this sort of thing.

Do I need to escape echo'd strings for console output?

I can't decide whether I'm being overly paranoid here, but if I'm running a PHP script from a commandline and that script echo's out user defined content, do I need to escape it?
For example, would this be potentially dangerous or would the text literally just echo out as plain text?
$test = 'shutdown -h now';
echo $test;
If I do need to escape, is it the escapeshellarg() function I want?
The shell interpretes commands from stdin but you are writing to stdout. So everything is fine
However, to prevent you from accidently copy pasting them into a terminal it is never a bad idea to escape them
I do not fully agree with the other answers.
It is right that you write to stdout, so the input won't be interpreted as commands, but some special control sequences can invoke some shell-related behaviour. See here, for example.
These cannot call other programs or commands, but they can annoy the user (he has to type reset for resseting the shell).
It should not be dangerous. User can't invoke any command this way. Of course if you are not using exec() or similar function in between. Please note that passing any argument to phpcli from command line is dangerous. Because this argument may contain "`", that executes the command in shell to get a result.

PHP exec() and spaces in paths

I'm executing the following in a PHP application:
$source = '/home/user/file.ext';
$output_dir = $this->setOutputString();
chdir('/home/ben/xc/phplib/bgwatcher-2011a/a01/');
exec('php bin/createjob.php $source $output_dir', $output);
return $output[0];
The problem is this: I have control over $source, but not $output_dir, which is a legacy Windows filesystem, and there are spaces in the path. An example $output_dir is:
/home/vol1/district id/store id/this_is_the_file.html
When inserting the output string into the exec() function, I have tried both:
addslashes($output_dir) and '"' . $output_dir . '"' to escape the entire output string. In the first case, the path gets concatenated to:
/home/vol1/districtthis_is_the_file.html
... where everything between the first space and the filename gets dropped. In the second case, exec() appears to throw a shoe and doesn't execute properly - unfortunately, the error message is getting lost in the machinery - I can provide it if it's absolutely necessary, but I'm also under time constraints to find a solution.
What's the solution, here? Do I sprintf() the entire string for exec()? I'm very confused as to why addslashes isn't working correctly to escape the spaces, and I assume it has something to do with sanitization with exec(), but I can't find any documentation to back it up.
Update: I've tried escapeshellarg() and preg_replace() without success. Thinking about this further, do I need to double-escape the path? Or escape the path and the command? If the path is being unescaped once by exec(), and once by PHP before it executes the command, does it stand to reason that I need to account for both escapes? Or is that not how it works?
I don't believe addslashes() does anything with spaces. escapeshellarg() might be what you want instead. Docs on escapeshellarg
From the PHP doc (here),
Returns a string with backslashes before characters that need to be quoted in database queries etc. These characters are single quote ('), double quote ("), backslash () and NUL (the NULL byte).
This won't do anything to the spaces. What you will need to do is use str_replace() to add slashes, like this:
$new_string = str_replace(" ", "\\ ", $old_string);
According to the PHP docs,
Returns a string with backslashes before characters that need to be quoted in database queries etc. These characters are single quote ('), double quote ("), backslash () and NUL (the NULL byte).
Looks like you'll have to preg_replace the spaces yourself.
Edit:
Even though this is the topic of another discussion, if performance is an issue, then after looking into it a little more, it seems that str_replace is actually quite a bit faster than preg_replace:
The test labeled "str_replace()" was
the faster by 0.9053 seconds (it took
10.3% the time.)
The first test took 1.0093 seconds. (preg_replace)
The second test took 0.104 seconds. (str_replace)
Benchmark found here.
I've used exec() with paths with spaces before, on both Windows and Linux hosts, and in both cases quoting the path worked perfectly for me.
That said, if you have no control over the safety of a shell argument, always run it through escapeshellarg() first!
You can very well use shell quotes, since that is what all exec commands run through:
exec("php bin/createjob.php '$source' '$output_dir'", $output);
It btw works not just for arguments, but also for the command itself:
exec('"/bin/echo" "one parameter"');
Use escapeshellcmd() anyway.
this works for me when using exec() with soffice(LibreOffice):
$file_name = "Some, file name.xlsx";
exec('/usr/bin/soffice --headless --convert-to pdf '."'".$file_name."'".' 2>&1', $output, $r);
You can use double quotes and escape character together to work out this.
$fileName = "filename with spaces.pdf";
exec("php bin/createjob.php >\"".$fileName."\" 2> error.log" , $output, $return);

Categories