Execute an external command by passing an array, with spaces in filenames - php

I have a PHP script that needs to execute programmes that will work on files that have spaces in the names. Most PHP functions for executing external commands (e.g. exec()) take an 1 string argument for the command line to execute. However then you have to do things like escapeshellarg() to make your input safe.
Is there some way to execute an external command in PHP with an array. So rather than:
exec("ls -l ".escapeshellarg($filename));
I can go:
exec(array("ls", "-l", $filename));
This would mean I don't have to worry about escaping the arguments. I want to avoid using escapeshellarg(), since the version I am using has a bug that strips out non-ASCII characters.
Java has this functionality http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Runtime.html#exec%28java.lang.String[]%29

Sounds like this isn't possible with PHP's builtin functions.

function myExec ( command, arguments )
{
exec( command + ' ' + implode( ' ', array_map( escapeshellarg, arguments ) ) );
}

Poke's answer is good - however, how many commands do they need to run? I would think about implementing a whitelist of commands and arguments - that way, you can be pretty darn sure they aren't injection malicious input. Something like:
$whitelistCommandArray = array('ls' => 'ls', ...);
if (isset($whitelistCommandArray[$userSuppliedCommand]])
{
//ok its a valid command, lets parse the args next
...
}
else echo "Unsupported command";
Update/edit:
Is a whitelist of arguments feasible?
What if OP needs to edit a multitude
of files? – Matchu
heh I dont know - it could be - totally depends on your needs.
$whitelistArray = array('ls' => array('a', 'l', 'h'), ...);
Something like that work - with both the command and then an array of arguments for it.

Related

Passing variable from PHP to perl is only reading first word

I am trying to pass a string from PHP into a Perl script, uppercase each word, and then pass the uppercase string back to PHP where I echo it out. I know I can do that in PHP, but there are other reasons why I will need to pass the string between PHP and Perl.
If the variable $user_input is "first second third" I want the echo statement in the PHP program to echo "FIRST SECOND THIRD", but right now it is only echoing "FIRST".
How do I get my Perl script to read in and uppercase each word, not just the first one? (The variable user_input is not necessarily fixed at a length of three words, it could be more or fewer.)
Here is the relevant PHP:
$result = shell_exec('/usr/bin/perl /var/www/my_site/myperl.pl ' . $user_input);
$user_input = $result;
echo $user_input;
And this is my Perl program (myperl.pl):
#!/usr/bin/perl
use warnings;
my $var1 = shift;
foreach $var ($var1){
print uc $var ;
}
I've tried changing several things in the Perl code (using #var1 for example) but can't seem to get it to work. Thanks in advance for any help you can provide.
Do not use shell_exec() with unescaped input. This function is really easy to use in an unsafe manner, and can lead to command injection security vulnerabilities. Here, you have run into issues around your lack of escaping. Slightly simplifying things, you are trying to execute the following command:
# shell_exec('/usr/bin/perl /var/www/my_site/myperl.pl ' . $user_input)
/usr/bin/perl /var/www/my_site/myperl.pl first second third
Since the variable contents are just pasted into the command line, it is split at spaces and passed to the command as separate arguments.
The safest approach is to bypass shell expansion and executing the intended program directly. Unfortunately, core PHP does not provide convenient functions for this task.
If you want to continue using the convenient shell_exec() function, you MUST escape the variable before constructing the shell command. For example:
shell_exec('/usr/bin/perl /var/www/my_site/myperl.pl ' . escapeshellarg($user_input))
This would lead to the following shell command being executed (note the single quotes around the argument):
/usr/bin/perl /var/www/my_site/myperl.pl 'first second third'
After that, your Perl code should work as expected.
Arguments passed to a Perl program are available in the builtin global #ARGV array.
The shift function takes an array name as an argument, and removes the first element from that array and returns it. When used without an argument it operates on #ARGV, or if in a subroutine on #_ array which holds arguments passed to the subroutine.
So the code you show takes the first argument passed to the program and works with it.† Then, the shown foreach loop goes over just that one scalar (single-valued) variable.
Instead, you want to extract or copy the whole #ARGV and then to iterate over that array ‡
use warnings;
use strict;
use feature qw(say);
my #args = #ARGV;
foreach my $arg (#args) {
say uc $arg; # with a new line
# print uc $arg; # no newline, all come out "stuck" together
}
Now you can process those (instead of just uc-ing them), what I presume is intended.
If you indeed wanted to merely print out upper-cased input then
say uc for #ARGV; # each on a line on its own
# print uc for #ARGV; # no newline between them
suffices. (The uc takes $_ by default.)
† If the PHP program passes one string to the Perl program, not expanded (broken into) words by a shell or some such, then your Perl code should work as it stands. But the code shown here works in that case as well, and it will always process all arguments.
‡ Or of course one can work with the #ARGV directly
foreach my $arg (#ARGV) { ... }
I copy it in the text to preserve it because normally it is parsed separately by libraries.

What is the safest way to access CLI program in PHP

I'm writing a PHP library that will need to reach out to the system and access a command line program that doesn't have a PHP interface (or PHP library). As such, I was wondering what is the best (and the safest way) to access the system to retrieve output from a CLI program? I've taken a look at both system() and exec(), but still not sure which is the best to use in a situation like this.
The library will get a string of user-passed text, and transmit it to the command line, retrieving back another string of text. Obviously, with passing user-provided data to the CLI, I will be doing a verification to ensure that no executable data can be passed.
I would suggest shell_exec() together with escapeshellcmd() and escapeshellarg().
To clarify (I was on the go when I first posted this answer): The right way to secure a shell command is:
$exe = 'cat';
$args = array('/etc/passwd');
$args = array_map('escapeshellarg', $args);
$escaped = escapeshellcmd($exe . ' ' . implode(' ', $args));
Here's a legitimate demo (and a nefarious demo as well) of the above code.
The above is just a dummy example, of course. But the main idea is that you apply escapeshellarg() to each argument and then call escapeshellcmd() on the whole command string (including the path to the executable and the previously escaped arguments). This is critical in arbitrary commands.
Note: By secure, I mean making it impossible to perform shell injection attacks by escaping characters that have special meaning like >, <, &&, | and more (see the Wikipedia link) while at the same time properly quoting spaces and other characters that may also have special interpretations by the shell.
With that aside, if you're already white-listing all the commands allowed, you already have the best possible security and you don't need the above functions (althought it doesn't hurt to use them anyway).
Regarding the actual calling function, they all pretty much do the same thing with a few quirks. Personally, I prefer shell_exec() since its return value is more versatile (from this page):
exec(): returns the last line of output from the command and flushes nothing.
shell_exec(): returns the entire output from the command and flushes nothing.
system(): returns the last line of output from the command and tries to flush the output buffer after each line of the output as it goes.
passthru(): returns nothing and passes the resulting output without interference to the browser, especially useful when the output is in binary format.
Except from the system() exit return code, you can mimic the behavior of all the other functions with the return value of shell_exec(). However, the inverse it's either harder to do, or just not possible.
I hope this clears things up for you.
Ideally, you would use passthru() from a pre-defined list of possible inputs (so that if user input == 'operation_a' you can { passthru('operation_a'); } without worrying about sanitizing input). Otherwise, use passthru() with some serious sanitation of input. passthru() allows you to capture the output of the command and pass the whole lump back to the browser. This function is particularly useful if you are expecting binary output (like from image generation, &c.).

PHP File Injection?

I have a script that calls a bash script that does some processing, but the script calls the bash script using user inputed data.
I am wondering if there is a way to make sure the person (it's a file upload) doesn't append like ;cd /;rm -rf * to the end of the file. Or anything else like that. Would a normal MYSQL Injection methods work? Is there a better alternative?
Being able to inject shell commands would be ... shell command injection, and neither file nor SQL injection. To secure against it, use escapeshellarg:
exec('bash bash-script ' . escapeshellarg($userInput));
Did you check escapeshellcmd() and escapeshellarg() or am I missing the point?
Securing this process is a two-way procedure:
ensuring the input meets some criteria (especially on maximum types)
ensuring the input cannot leak and change the process itself
Let's say I'm passing a number to a program...
$num = $_GET['num']; // get the input
$num = (int)$_GET['num']; // ensure it is an integer
$num = max($num, 0); // ensure it is at least 0
$num = min($num, 800); // ensure it is at most 800
$num = escapeshellarg($num); // this is overkill at this point, but you never know
exec('command '.$num);
As advised above, you can also have your own little language to do this but...
it may still be vulnerable
it may be overkill for a simple task
it is just an advanced version of the filter system
Finally, there's another alternative. There are functions that accept the command and parameters as separate arguments, such as popen() (you can push command arguments through pipes). But this depends on implementation.

PHP passing variables to Perl, having issues with using spaces between variables method, is there another, better way?

I find myself needing to use Perl more and more since PHP's regular expressions leave me wondering what is going on half the time as they simply don't respond as they should (as they do when I use Perl, for example)... i had been using this method to call perl (before i started trying to do this with regular expressions).. passing in a regex string as a variable causes all sorts of problems, such as using " ( )" anywhere makes it not work, among other things.. I am wondering if there is a better way to do this, than the string of variables after the perl filename method as it seems to have some glaring limiations.. thanks for any info.
the way I do it currently:
$file = "/pathtomy/perlscript.pl $var1 $var2 $var3" ;
ob_start();
passthru($file);
$perlreturn = ob_get_contents();
ob_end_clean();
return $perlreturn;
For the general case, you'll want to use escapeshellarg. Blindly wrapping everything in single quotes works most of the time but will fail when one of your arguments contains a single quote!
string escapeshellarg ( string $arg )
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. This function should be used to escape individual arguments to shell functions coming from user input. The shell functions include exec(), system() and the backtick operator.
Using a simple Perl program that prints its command-line arguments
#! /usr/bin/perl
$" = "]["; # " fix StackOverflow highlighting
print "[#ARGV]\n";
and then a modified version of the PHP program from your question
<?php
$var1 = "I'm a";
$var2 = "good boy,";
$var3 = "I am.";
$file = "./prog.pl " . implode(" ",
array_map("escapeshellarg",
array($var1,$var2,$var3)));
echo "command=", $file, "\n";
ob_start();
passthru($file);
$perlreturn = ob_get_contents();
ob_end_clean();
echo "return=", $perlreturn;
?>
we see the following output:
$ php prog.php
command=./prog.pl 'I'\''m a' 'good boy,' 'I am.'
return=[I'm a][good boy,][I am.]
Try to use quotation marks:
$file = "/pathtomy/perlscript.pl '$var1' '$var2' '$var3'" ;

Disable globbing in PHP exec()

I have a PHP script which calls another script in order to add IP addresses to a whitelist. I sometimes want to whitelist all addresses, in which case I have it call
exec("otherscript *.*.*.*", output, retval);
This worked fine, adding the string "*.*.*.*" to the whitelist until I happened to have another file in the directory of the php script that matched that pattern ("foo.1.tar.gz"), at which point the wildcards were expanded, and I ended up with the filename in my whitelist. Is there some way to disable globbing in php's exec? It isn't mentioned in the PHP docs as far as I can tell.
escapeshellarg will make sure your string is safe for using as a shell argument. Globbing is probably not mentioned in the manual because it's up to the shell, and also differs between different shells.
$address = escapeshellarg('*.*.*.*');
exec("otherscript $address", $output, $retval);
Quoting the parameter should help:
exec("otherscript '*.*.*.*'", output, retval);

Categories