I am using PHP to generate a shell command that I then pass to PHP's exec() function. I am extensively using escapeshellarg() to make sure there aren't any security issues and also to ensure that certain Linux shell attributes and operators are being applied in the right spots (like stream redirects).
So here is a general example of my problem:
I am nesting escapeshellarg() three times, like this:
echo "first_program -f " . escapeshellarg("second_program -f ". escapeshellarg("third_program -f " . escapeshellarg("value")));
The output of this is:
first_program -f 'second_program -f '\''third_program -f '\''\'\'''\''value'\''\'\'''\'''\'''
As you can see, once it gets to the third escapeshellarg() function call, it starts going crazy with escaping characters.
When this gets executed by the exec() function, it doesn't un-escape it correctly and therefore doesn't execute correctly.
Am I missing something? Or does the escapeshellarg() function break if you nest it like this? It works only nesting it twice, but then on the third nesting it goes crazy. Should I be using escapeshellcmd() in some places? I'm passing all of these in as arguments so I don't see why I wouldn't use the escapeshellarg().
Related
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.
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.).
I have a php script that handles a form input. For design reasons both a bit out of my control, and which I do not entirely wish to change, I have to call a perl script with the parameters specified in the html form.
I sanitized all inputs and then output them to a file called input, which is read by the perl script named, for sake of brevity in this question, script.pl. Script.pl should do some stuff and then write all outputs to a file named output.
I call the perl script from php like so:
system('perl script.pl 2>errors');
No good, nothing happens. output is not created, errors is not created, and the side effect does not occur.
My apache runs as www-data user and group id. My directory is set with 775 settings with ownership as me:www-data. (My user name is replaced with "me" for sakes for privacy).
My question is two fold:
1) Am I doing this wrong? If so how should I improve upon the code?
2) Is there a more sane way to catch errors in system execution?
After programming in perl for a while, php feels like a pain in the ass.
OS: Ubuntu server edition
popen can be used to get the shell's response. that is your best bet. Which can help you debug why system is angry. also, if your pl is saying "hello" and "bye", popen can even read that.
If the command to be executed could not be found, a valid resource is returned. This may seem odd, but makes sense; it allows you to access any error message returned by the shell
Ideally, I would have taken data from stdin and written to stdout. popen would allow neat access to both.
popen('pwd;perl script.pl 2>errors;echo done');
then you can see where were you (directory) when system got called and did it "done".
In the past I have used shell_exec() or backticks to accomplish this.
The documentation for shell_exec's return value indicates it is identical to the backtick operator:
Return Values
The output from the executed command.
Hope that helps.
system() only returns the status code.
$var = shell_exec ("ls");
print $var;
$var = `ls -l`;
print $var;
Is perl in the path? Maybe you need to specify it fully (e.g. /usr/bin/perl). Is system() returning false, indicating a failure? If you try something simpler, like system('/usr/bin/true', $retval), does $retval get set to 1?
Take a look at the PHP system() documentation. The following is the function prototype of system():
string system ( string $command [, int &$return_var ] )
Pass in a 2nd argument and then print out the return string as well as the second variable. See what the error says.
How do I use a PHP5 variable inside a system() call
$dir = '/etc/somedir';
eg system("ls $dir")
I think I'm missing something
I am actually passing a variable from a post
e.g.
$username = $_POST[username];
to a system call
system("processData --user $username --pass $password");
this isn't working, so i trivialised down to a simple
example
You are doing it fine except that you are missing semi-colon (;)
system("ls $dir");
You can also do like:
system("ls" . $dir);
Note:
When allowing user-supplied data to be
passed to this function, use
escapeshellarg() or
escapeshellcmd() to ensure that
users cannot trick the system into
executing arbitrary commands.
it looks like you are not.. could you extend your example? are there any errors returned? what does the system() function return?
you should keep in mind that system() returns only the last line of the run command.
also, instead of 'ls' you can use a built-in php function like dir() or DirectoryIterator
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.