I'm creating some code that will upload the contents of a word document to a server, extract its text, and insert it into a database.
exec("PATH=$PATH:/home1/myserver/bin && antiword " .
$_FILES['file']['tmp_name'], $mycontent);
For some bizarre reason, $mycontent is always an empty array. Google wasn't that helpful. Does anyone know what I'm doing wrong?
The $PATH in your exec quote is trying to be converted to whatever your PHP $PATH is rather than the BASH $PATH.
You can either escape the $ (\$) or use single quotes.
In general, you should be using escapeshellarg() or escapeshellcmd() to make things a bit safer. It would have prevented this situation. Also if you call exec() with user inputs, it will help prevent them from escaping your command and calling their own malicious shell commands.
EDIT
Actually, you might have issues with your filename/path for some reason. Just start simple.
Does this work:
exec('/home1/myserver/bin/antiword ' .
escapeshellarg($_FILES['file']['tmp_name']), $mycontent);
If not, what is this:
echo '/home1/myserver/bin/antiword ' .
escapeshellarg($_FILES['file']['tmp_name']);
You'll have to create a file to test, and substitute it for the file in $_FILES. But does that work directly from the commandline?
Related
I just cannot fathom how to get the PHP exec() or shell_exec() functions to treat a '*' character as a wildcard. Is there some way to properly encode / escape this character so it makes it through to the shell?
This is on windows (via CLI shell script if that matters, Terminal or a git-bash yields the same results).
Take the following scenario:
C:\temp\ contains a bunch of png images.
echo exec('ls C:\temp\*');
// output: ls: cannot access 'C:\temp\*': No such file or directory
Permissions is not the problem:
echo exec('ls C:\temp\exmaple.png');
// output: C:\temp\example.png
Therefore the * character is the problem and is being treated as a literal filename rather than a wildcard. The file named * does not exist, so from that point of view, it's not wrong...
It also does not matter if I use double quotes to encase the command:
echo exec("ls C:\temp\*");
// output: ls: cannot access 'C:\temp\*': No such file or directory
I have also tried other things like:
exec(escapeshellcmd('ls C:\temp\*'));
exec('ls C:\temp\\\*');
exec('ls "C:\temp\*"');
exec('ls "C:\temp\"*');
And nothing works...
I'm pretty confused that I cannot find any other posts discussing this but maybe I'm just missing it. At this point I have already worked around the issue by manually programming a glob loop and using the internal copy() function on each file individually, but it's really bugging me that I do not understand how to make the wildcard work via shell command.
EDIT:
Thanks to #0stone0 - The answer provided did not particularly answer my initial question but I had not tried using forward slashes in the path and when I do:
exec('ls C:/temp/*')
It works correctly, and as 0stone0 said, it only returns the last line of the output, which is fine since this was just for proof of concept as I was not actually attempting to parse the output.
Also, on a side note, since posting this question my system had been updated to Win11 22H2 and now for some reason the original test code (with the backslashes) no longer returns the "Cannot access / no file" error message. Instead it just returns an empty string and has no output set to the &$output parameter either. That being said, I'm not sure if the forward slashes would have worked on my system prior to the 22H2 update.
exec() only returns the last output line by default.
The wildcard probably works, but the output is just truncated.
Pass an variable by ref to exec() and log that:
<?php
$output = [];
exec('ls -lta /tmp/*', $output);
var_dump($output);
Without any additional changes, this returns the same as when I run ls -lta /tmp/* in my Bash terminal
That said, glob() is still the preferred way of getting data like this especcially since
You shouldn't parse the output of ls
How do I properly execute commands in the command line using php? For example I'm using the command below in the command line to convert a docx file into a pdf file:
pdfcreator.exe /PF"D:\Documents\sample.docx
Now using PHP code I want to be able to execute the same command but nothing seems to be happening:
<?php
shell_exec('pdfcreator.exe /PF"D:\Documents\sample.docx"');
?>
Is this possible in PHP?If yes, how do I do it?
system("c:\\path\\to\\pdfcreator.exe /PF\"D:\\Documents\\sample.docx"");
try this.
Don't forget to escape your command with escapeshellcmd(). This will prevent you from having to use ugly backslashes and escape characters.
There are also other alternatives which may work:
`command` // back ticks drop you out of PHP mode into shell
exec('command', $output); // exec will allow you to capture the return of a command as reference
shell_exec('command'); // will return the output to a variable
system(); //as seen above.
Also, make sure your .exe is included within your $PATH variable. If not, include the full path for the command.
I was running over a sample script and hit on this particular issue.
The script starts off by setting
$docroot=$_SERVER['DOCUMENT_ROOT'];
For writing to a file,
#$fp = fopen("$docroot/../parts/partsorders.txt",'ab');
is used. But no matter what, this fails to write to the file.
After a tinkering with it for a while, I set the command to
#$fp = fopen('$docroot/../parts/partsorders.txt','ab');
used single quotes instead of double quotes and it worked fine!
My question is, isn't the former double quoted format supposed to work instead of the single quotes. What is happening here ?
Here is the stripped down code, guys (Assume that the file exists in the server) :
<?php
$docroot=$_SERVER['DOCUMENT_ROOT'];
$outputstring = "herpderp";
?>
<html>
<head>
<title>Quotes</title>
</head>
<body>
<?php
#$fp=fopen("$docroot/../parts/partsorders.txt","ab");
flock($fp, LOCK_EX);
if(!$fp) {
echo "<p><strong>Failed.</strong></p></body></html>";
exit;
}
fwrite($fp,$outputstring,strlen($outputstring));
flock($fp,LOCK_UN);
fclose($fp);
echo "<p>Order written.</p>";
?>
</body>
</html>
There are differences between single and double quoted strings in PHP. Single quoted strings do not cause the expansion of variable names while double quoted strings do. See here. Why your code works when you use the variable name with single quoted strings doesn't make sense to me. Furthermore, it's a bad idea to use # in front of your fopen commands, it will prevent you from seeing the error output.
The double quote one is the one that should evaluate $docroot for you. What the single quote should do is try to open a file that actually has $docroot as a string in it's path. Does
#$fp = fopen($docroot . '/../parts/partsorders.txt','ab');
yield the same result?
And do you use #to supress errors? In that case it should be before the function I think
$fp = #fopen($docroot . '/../parts/partsorders.txt','ab');
But don't do that when trying to find errors in your application. The thing is that you could very well get an error that it can't find the file and you just don't notice.
UPDATE
Thanks to pinkgothic for his comment, firstly for providing the correct terminology and secondly for pointing out that this answer is wrong.
I have experimented with the theory that shell is trying to expand the environment variable $docroot and found that shell expansion is not possible in fopen().
I tried the following code and got this error (I have a file called test.txt in my home directory)
$fp = fopen( '$HOME/test.txt', 'rb' );
//PHP Warning: fopen($HOME/test.txt): failed to open stream: No such file or directory in php shell code on line 1
So unless the OP has some configuration which allows shell expansion in fopen(), my answer is, as I say, incorrect.
The string $docroot/../parts/partsorders.txt is being sent directly to the operating system shell. As there is no $docroot variable set it is replaced by an empty string, so it is the same as using
#$fp = fopen('/../parts/partsorders.txt','ab');
Which is an absolute path starting from /, the document root.
#run from shell (bash)
~$ echo $docroot/../parts/partsorders.txt
/../parts/partsorders.txt
After my comment, I had a night's sleep and realised fopen() may not actually use/be like realpath() (which expects all segments to be set, even the ones that are irrelevant for the final normalised path).
It doesn't.
Accordingly, the reason your file is opened is actually fairly simple:
'$docroot/../parts/partsorders.txt'
...which is read as "pastorders.txt in the 'parts' folder which is a folder found in the folder above the '$docroot' folder which is in the current working directory" collapses to...
'parts/partsorders.txt'
...because fopen() simply vanishes $docroot/.. away without checking that $docroot exists. (Functions like realpath() do check it, which was throwing me off.)
So, your file is actually in <current working directory>/parts/partsorders.txt. (Since you're opening the file with the a flag, if it didn't exist there before, it was definitely created there.)
Whatever your $_SERVER['DOCUMENT_ROOT'] contains, it seems it's not what you want. Additionally, in some setups, you can't reasonably .. above $_SERVER['DOCUMENT_ROOT'] - permissions actually won't let you.
If that environment variable outright isn't set (if that's even possible; but I think this demonstrates the problem even if it isn't), the path is quite different:
"$docroot/../parts/partsorders.txt"
...becomes:
"/../parts/partsorders.txt"
...which tries to get up the hierarchy past the root point (/), which would of course not work.
I suggest echoing out or logging $_SERVER['DOCUMENT_ROOT'] and taking a look at what it actually contains, and if it's what you expect it to be.
What might be worth looking for is __DIR__ (or in older PHP versions, dirname(__FILE__)), which takes the directory the file. As long as the file knows where it is, you can just read out files relative to its location.
I'm writing a setup/installer script for my application, basically just a nice front end to the configuration file. One of the configuration variables is the executable path for mysql. After the user has typed it in (for example: /path/to/mysql-5.0/bin/mysql or just mysql if it is in their system PATH), I want to verify that it is correct. My initial reaction would be to try running it with "--version" to see what comes back. However, I quickly realised this would lead to me writing this line of code:
shell_exec($somethingAUserHasEntered . " --version");
...which is obviously a Very Bad Thing. Now, this is a setup script which is designed for trusted users only, and ones which probably already have relatively high level access to the system, but still I don't think the above solution is something I want to write.
Is there a better way to verify the executable path? Perhaps one which doesn't expose a massive security hole?
Running arbitrary user commands is like running queries based on user input... Escaping is the key.
First, validate if it is an executable using is_executable().
PHP exposes two functions for this: escapeshellarg() and escapeshellcmd().
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.
escapeshellcmd() escapes any characters in a string that might be used to trick a shell command into executing arbitrary commands.
This should limit the amount of risk.
if(is_executable($somethingAUserHasEntered)) {
shell_exec(escapeshellarg($somethingAUserHasEntered) . " --version");
}
After all, doing rm --version isn't very harmful, and "rm -rf / &&" --version will get you anywhere very fast.
EDIT: Since you mentioned PATH... Here is a quick function to validate if the file is an executable according to PATH rules:
function is_exec($file) {
if(is_executable($file)) return true;
if(realpath($file) == $file) return false; // Absolute Path
$paths = explode(PATH_SEPARATOR, $_ENV['PATH']);
foreach($paths as $path) {
// Make sure it has a trailing slash
$path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
if(is_executable($path . $file)) return true;
}
return false;
}
You could try a simple file_exists call to determine if something exists at that location, along with an is_executable to confirm that it's something you can run.
have you looked at is_dir() or is_link() or is_file() or is_readable()
Hope these help.
system('which '.escapeshellarg($input)) will give you the absolute path to the executable, regardless if it's just the name or an absolute path.
My so far not-so-bad version to implement this is:
function bashFileConvert($file)
{
return preg_replace('/([^\/\s]+\s+[^\/]+)(\/|$)/','"${1}"${2}',$file);
}
which mostly processes the problem when there is a space in the file name,like
$flie = '/usr/local/my test file.txt'
while will not be recognizable for Bash.
So I need to convert to
$file = '/usr/local/"my test file.txt"'
before calling something like:
exec('ls ' . $file);
But there are still many other corner cases, like the quote and '&' problem.
So, is there a ready version to do this job?
==================================
Now I tried escapeshellarg(), but it is a little strange here:
$file = '/usr/local/apache2/resumes_txt/5/San Francisco/qtzhang/Device "Engineer"/Job Resume Qintao Zhang.pdf.txt';
echo escapeshellarg($file);
D:\\test>php test.php
"/usr/local/apache2/resumes_txt/5/San Francisco/qtzhang/Device Engineer /Job Resume Qintao Zhang.pdf.txt"
It seems with this function, a quote is replaced with a space.
The solution is to use the escapeshellarg function in PHP (http://uk.php.net/manual/en/function.escapeshellarg.php):
$file = escapeshellarg('/usr/local/my test file.txt');
exec('ls ' . $file);
It will wrap quotes round it and escape quotes for you.