Run executable from php without spawning a shell - php

I need to call an executable from an imposed context of a PHP script. Both performance and security wise it's better not to call a shell at all between web server process and executable.
Of course I searched the web, without success (in such a PHP context). Many other languages allow that and document it clearly.
Alas, backticks, exec(), shell_exec(), passthru(), system(), proc_open(), popen() call a shell.
And pcntl_fork() seems unavailable.
How to test if a function calls a shell or not.
This was tested on a Debian 6 64bit with PHP 5.3.3-7+squeeze15 .
Test code on http://pastebin.com/y4C7MeJz
To get a meaningful test I used a trick which is to ask to execute a shell command not also available as an executable. A good example is umask . Any function returning something like 0022 definitely called a shell. exec(), shell_exec(), passthru(), system(), proc_open() all did.
See detailed results on http://pastebin.com/RBcBz02F .
pcntl_fork fails
Now, back the the goal : how to execute arbitrary program without launching a shell ?
Php's exec takes as expected an array of string args instead of a unique string. But pcntl_fork just stops the requests without even a log.
Edit: pcntl_fork failure is because the server uses Apache's mod_php, see http://www.php.net/manual/en/function.pcntl-fork.php#49949 .
Edit: added popen() to the tests, following #hakre suggestion.
Any hint appreciated.

To answer your sentence :
Both performance and security wise it's better not to call a shell at
all between web server process and executable.
About performances, well, yes, php internals forks, and the shell itself forks too so that's a bit heavy. But you really need to execute a lot of processes to consider those performances issues.
About security, I do not see any issue here. PHP has the escapeshellarg function to sanitize arguments.
The only real problem I met with exec without pcntl is not a resource nor security issue : it is really difficult to create real deamons (without any attachment to its parent, particularily Apache). I solved this by using at, after double-escaping my command:
$arg1 = escapeshellarg($arg1);
$arg2 = escapeshellarg($arg2);
$command = escapeshellarg("/some/bin $arg1 $arg2 > /dev/null 2>&1 &");
exec("$command | at now -M");
To get back to your question, the only way I know to execute programs in a standard (fork+exec) way is to use the PCNTL extension (as already mentionned). Anyway, good luck!
To complete my answer, you can create an exec function yourself that does the same thing as pcntl_fork+pcntl_exec.
I made a my_exec extension that does a classic exec+fork, but actually, I do not think it will solve your issues if you're running this function under apache, because the same behaviour as pcntl_fork will apply (apache2 will be forked and there may be unexpected behaviours with signal catching and so on when execv does not succeed).
config.m4 the phpize configuration file
PHP_ARG_ENABLE(my_exec_extension, whether to enable my extension,
[ --enable-my-extension Enable my extension])
if test "$PHP_MY_EXEC_EXTENSION" = "yes"; then
AC_DEFINE(HAVE_MY_EXEC_EXTENSION, 1, [Whether you have my extension])
PHP_NEW_EXTENSION(my_exec_extension, my_exec_extension.c, $ext_shared)
fi
my_exec_extension.c the extension
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define PHP_MY_EXEC_EXTENSION_VERSION "1.0"
#define PHP_MY_EXEC_EXTENSION_EXTNAME "my_exec_extension"
extern zend_module_entry my_exec_extension_module_entry;
#define phpext_my_exec_extension_ptr &my_exec_extension_module_entry
// declaration of a custom my_exec()
PHP_FUNCTION(my_exec);
// list of custom PHP functions provided by this extension
// set {NULL, NULL, NULL} as the last record to mark the end of list
static function_entry my_functions[] = {
PHP_FE(my_exec, NULL)
{NULL, NULL, NULL}
};
// the following code creates an entry for the module and registers it with Zend.
zend_module_entry my_exec_extension_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_MY_EXEC_EXTENSION_EXTNAME,
my_functions,
NULL, // name of the MINIT function or NULL if not applicable
NULL, // name of the MSHUTDOWN function or NULL if not applicable
NULL, // name of the RINIT function or NULL if not applicable
NULL, // name of the RSHUTDOWN function or NULL if not applicable
NULL, // name of the MINFO function or NULL if not applicable
#if ZEND_MODULE_API_NO >= 20010901
PHP_MY_EXEC_EXTENSION_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
ZEND_GET_MODULE(my_exec_extension)
char *concat(char *old, char *buf, int buf_len)
{
int str_size = strlen(old) + buf_len;
char *str = malloc((str_size + 1) * sizeof(char));
snprintf(str, str_size, "%s%s", old, buf);
str[str_size] = '\0';
free(old);
return str;
}
char *exec_and_return(char *command, char **argv)
{
int link[2], readlen;
pid_t pid;
char buffer[4096];
char *output;
output = strdup("");
if (pipe(link) < 0)
{
return strdup("Could not pipe!");
}
if ((pid = fork()) < 0)
{
return strdup("Could not fork!");
}
if (pid == 0)
{
dup2(link[1], STDOUT_FILENO);
close(link[0]);
if (execv(command, argv) < 0)
{
printf("Command not found or access denied: %s\n", command);
exit(1);
}
}
else
{
close(link[1]);
while ((readlen = read(link[0], buffer, sizeof(buffer))) > 0)
{
output = concat(output, buffer, readlen);
}
wait(NULL);
}
return output;
}
PHP_FUNCTION(my_exec)
{
char *command;
int command_len, argc, i;
zval *arguments, **data;
HashTable *arr_hash;
HashPosition pointer;
char **argv;
// recovers a string (s) and an array (a) from arguments
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &command, &command_len, &arguments) == FAILURE) {
RETURN_NULL();
}
arr_hash = Z_ARRVAL_P(arguments);
// creating argc and argv from our argument array
argc = zend_hash_num_elements(arr_hash);
argv = malloc((argc + 1) * sizeof(char *));
argv[argc] = NULL;
for (
i = 0, zend_hash_internal_pointer_reset_ex(arr_hash, &pointer);
zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS;
zend_hash_move_forward_ex(arr_hash, &pointer)
)
{
if (Z_TYPE_PP(data) == IS_STRING) {
argv[i] = malloc((Z_STRLEN_PP(data) + 1) * sizeof(char));
argv[i][Z_STRLEN_PP(data)] = '\0';
strncpy(argv[i], Z_STRVAL_PP(data), Z_STRLEN_PP(data));
i++;
}
}
char *output = exec_and_return(command, argv);
// freeing allocated memory
for (i = 0; (i < argc); i++)
{
free(argv[i]);
}
free(argv);
// WARNING! I guess there is a memory leak here.
// Second arguemnt to 1 means to PHP: do not free memory
// But if I put 0, I get a segmentation fault
// So I think I do not malloc correctly for a PHP extension.
RETURN_STRING(output, 1);
}
test.php a usage sample
<?php
dl("my_exec.so");
$output = my_exec("/bin/ls", array("-l", "/"));
var_dump($output);
shell script run those commands, of course use your own module directory
phpize
./configure
make
sudo cp modules/my_exec_extension.so /opt/local/lib/php/extensions/no-debug-non-zts-20090626/my_exec.so
Result
KolyMac:my_fork ninsuo$ php test.php
string(329) ".DS_Store
.Spotlight-V100
.Trashes
.file
.fseventsd
.hidden
.hotfiles.btree
.vol
AppleScript
Applications
Developer
Installer Log File
Library
Microsoft Excel Documents
Microsoft Word Documents
Network
System
Users
Volumes
bin
cores
dev
etc
home
lost+found
mach_kernel
net
opt
private
sbin
tmp
usr
var
vc_command.txt
vidotask.txt"
I am not a C dev, so I think there are cleaner ways to achieve this. But you get the idea.

In PHP 7.4+, proc_open open processes directly if cmd is passed as array.
As of PHP 7.4.0, cmd may be passed as array of command parameters. In this case the process will be opened directly (without going through a shell) and PHP will take care of any necessary argument escaping.
So this example:
<?php
$file_descriptors = [
0=>['pipe','r'],
1=>['pipe','w'],
2=>['pipe','w']
];
$cmd_string = 'ps -o comm=';
$cmd_array = [
'ps',
'-o',
'comm='
];
// This is executed by shell:
$process = proc_open($cmd_string,$file_descriptors,$pipes);
$output = stream_get_contents($pipes[1]);
$return = proc_close($process);
printf("cmd_string:\n%s\n",$output);
// This is executed directly:
$process = proc_open($cmd_array,$file_descriptors,$pipes);
$output = stream_get_contents($pipes[1]);
$return = proc_close($process);
printf("cmd_array:\n%s\n",$output);
outputs:
cmd_string:
bash
php
sh
ps
cmd_array:
bash
php
ps

Id consider trying pcntl_exec()

Related

shell_exec command in php not working properly

I am trying to execute a C program using the shell_exec command, which needs arguments to be passed. It is working for one input, but not working for others. I tried to run the C program through terminal, it is working for all the inputs.
This is my execprog.php file. I have to give 2 inputs as command line arguments to file. /var/www/project is the path.
$query = "/var/www/project/./a.out /var/www/project/constraints.txt /var/www/project/constraints_keyword.txt /var/www/project/FIB.txt /var/www/project/ANS.txt";
echo $query;
$var = shell_exec($query);
echo $var;
<?php
$query = "/var/www/project/./a.out";
$arguments = array
(
'/var/www/project/constraints.txt',
'/var/www/project/constraints_keyword.txt',
'/var/www/project/FIB.txt',
'/var/www/project/ANS.txt'
);
$string = '';
for($i=0;$i<count($arguments);$i++)
$string.= ' %s';
$command = vsprintf("{$query}{$string}", $arguments);
$var = shell_exec($command);
echo $var;
As you it works on the terminal and not on apache then apache's php.ini file may be disabling the use of shell_exec().
See http://www.php.net/manual/en/ini.core.php#ini.disable-functions
Your apache's php.ini file may look something like
disable_functions=exec,passthru,shell_exec,system,proc_open,popen
Remove shell_exec from this list and restart the web server, although this is a security risk and I don't recommend it.
In general functions such as exec,shell_exec and system are always used to execute the external programs. Even a shell command can also be executed. If these two functions are enabled then a user can enter any command as input and execute into your server. So usually people disable in apache config as disable_functions to secure their site.
It works for me - Here is test run
Sample test c code
[akshay#gold tmp]$ cat test.c
#include<stdio.h>
int main(int args, char *argv[]) {
int i = 0;
for (i = 0; i < args; i++)
printf("Arg[%d] = %s\n",i, argv[i]);
return 0;
}
Compile
[akshay#gold tmp]$ gcc test.c
Sample php script
[akshay#gold tmp]$ cat test.php
<?php
$query = "/tmp/./a.out /var/www/project/constraints.txt /var/www/project/constraints_keyword.txt /var/www/project/FIB.txt /var/www/project/ANS.txt";
$var = shell_exec($query);
echo $var;
?>
Execution and output
[akshay#gold tmp]$ php test.php
Arg[0] = /tmp/./a.out
Arg[1] = /var/www/project/constraints.txt
Arg[2] = /var/www/project/constraints_keyword.txt
Arg[3] = /var/www/project/FIB.txt
Arg[4] = /var/www/project/ANS.txt

Cannot invoke another function within a PHP_FUNCTION() in C

I need to call a function within a PHP_FUNCTION() function in C to extend PHP, this is a multithread script and The function itself works perfectly using int main(). Here is what I try to achieve.
#define NUM_THREADS 3
char *messages[NUM_THREADS];
void *PrintHello(void *threadid)
{
zend_printf("gP");
int *id_ptr, taskid;
sleep(4);
id_ptr = (int *) threadid;
taskid = *id_ptr;
zend_printf("Thread %d: %s\n", taskid, messages[taskid]);
pthread_exit(NULL);
}
PHP_FUNCTION(hello_world)
{
pthread_t threads[NUM_THREADS];
int *taskids[NUM_THREADS];
int rc, t;
messages[0] = "English: Hello World!";
messages[1] = "French: Bonjour, le monde!";
messages[2] = "Spanish: Hola al mundo";
for(t=0; t < NUM_THREADS; t++)
{
taskids[t] = (int *) malloc(sizeof(int));
*taskids[t] = t;
zend_printf("Creating thread %d\n <br>", t);
rc = pthread_create(&threads[t], NULL, (void* (*) (void*)) pthreads_routine, (void *) taskids[t]);
if (rc) {
zend_printf("ERR; pthread_create() ret = %d\n", rc);
}
}
}
I need to call PrintHello() function from
rc = pthread_create(&threads[t], NULL, PrintHello, (void *) taskids[t]);
Do I also need to register void *PrintHello(void *threadid) in
const zend_function_entry my_functions[] = {
PHP_FE(hello_world, NULL)
PHP_FE_END
};
the var_dump() out put is
Creating thread 0
Creating thread 1
Creating thread 2
NULL
at the top of void *PrintHello(void *threadid) function I have included zend_printf("gP"); line to make sure that the function is invoked and from the looks of out put it is obvious that the function is not invoked.
My environment is Mac OSX , xCode 7.2, PHP 7.0.1
What am I doing wrong?
It appears you have two problems in your code, both of which explain why you're not getting any output.
1) At least in my tests, it appears that zend_printf -> php_printf -> vspprintf is not thread safe. Your code always crashes for me once one of the threads attempts to call zend_printf(). But, even if that weren't the case, there's also:
2) Assuming your php code looks like this:
<?php
hello_world();
What's happening is that when you call pthread_create(), it returns immediately having created the thread, though the thread has not necessarily started to run. Then, hello_world returns once all the threads have been created. Then, your main thread ends because there's nothing else to do.
Once the main thread ends, your spawned threads are immediately terminated. If you're seeing nothing at all, that's because the main thread ends before any of the pthreads are actually scheduled, before they even execute your zend_printf("gP"); line.
If you change your php code to:
<?php
hello_world();
sleep(10);
Then you give the child threads enough time to be scheduled and given CPU time (at which point they will probably crash calling the first zend_printf), and if not, give them enough time to make it past the sleep(4) to get to the zend_printf(Thread id).
If you replace your PrintHello with this:
void *PrintHello(void *threadid)
{
int *id_ptr, taskid;
id_ptr = (int *) threadid;
taskid = *id_ptr;
printf("Thread %d: start\n", taskid, messages[taskid]);
sleep(4);
printf("Thread %d: %s\n", taskid, messages[taskid]);
pthread_exit(NULL);
}
(replace the zend_printf with regular printf), then you'll get your desired output, at least on the cli.

C++ WinInet only making 1 request

I'm having trouble using WinInet locally. I have a local Apache webserver (xampp) which is running nicely, the problem is that when I try to make a GET request to a PHP script, it seems to be only doing it once. The PHP script simply outputs a random number, and I see the same number 3 times (the script isn't at fault). I've also checked the Apache access log and it only shows up once. The odd thing is that when NOT using it locally, the loop works perfectly and it does send out multiple requests (Wireshark shows this too).
Here is the code, simplified and still giving problems:
#include <Windows.h>
#include <WinInet.h>
#include <iostream>
#include <string>
#pragma comment(lib, "wininet.lib")
void req()
{
HINTERNET hInternet = InternetOpenW(L"useragent", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
HINTERNET hConnect = InternetConnectW(hInternet, L"127.0.0.1", 80, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL);
HINTERNET hRequest = HttpOpenRequestW(hConnect, L"GET", L"test/test.php", NULL, NULL, NULL, 0, 0);
BOOL bRequestSent = HttpSendRequestW(hRequest, NULL, 0, NULL, 0);
std::string strResponse;
const int nBuffSize = 1024;
char buff[nBuffSize];
BOOL bKeepReading = true;
DWORD dwBytesRead = -1;
while (bKeepReading && dwBytesRead != 0)
{
bKeepReading = InternetReadFile(hRequest, buff, nBuffSize, &dwBytesRead);
strResponse.append(buff, dwBytesRead);
}
std::cout << strResponse << std::endl;
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
}
int main()
{
for (int x = 0;x < 3; x++) // 3 times
{
req();
Sleep(2000);
}
system("PAUSE");
}
I can't seem to figure this out...
I am unable to reproduce your observation if I query the for example www.google.com.
I think that your program simply continues reading the response with InternetReadFile but the other process on your machine has not yet completed the request. I suggest to wait for the request to complete using WinHttpReceiveResponse before reading the response contents.
Some more things concerning your implementation:
You do not need to call InternetOpen(..) for each new request. Is is sufficient to do this once and to store the returned handle until your application terminates.
It is very important to check for errors! Your function simply relies on all calls to succeed which obviously doesn't seem to be the case due to your question...

PHP extension seg fault when modifying a zval by reference

PHP:
$publickey = pack('H*', "03ca473d3c0cccbf600d1c89fa33b7f6b1f2b4c66f1f11986701f4b6cc4f54c360");
$pubkeylen = strlen($publickey);
$result = secp256k1_ec_pubkey_decompress($publickey, $pubkeylen);
C extension:
PHP_FUNCTION(secp256k1_ec_pubkey_decompress) {
secp256k1_start(SECP256K1_START_SIGN);
zval *pubkey, *pubkeylen;
unsigned char* newpubkey;
int newpubkeylen;
int result;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &pubkey, &pubkeylen) == FAILURE) {
return;
}
newpubkey = Z_STRVAL_P(pubkey);
newpubkeylen = Z_LVAL_P(pubkeylen);
result = secp256k1_ec_pubkey_decompress(newpubkey, &newpubkeylen);
if (result == 1) {
newpubkey[newpubkeylen] = 0U;
ZVAL_STRINGL(pubkey, newpubkey, newpubkeylen, 0);
ZVAL_LONG(pubkeylen, newpubkeylen);
}
RETURN_LONG(result);
}
the $publickey is decompressed from a 32 byte to a 65 byte string, for w/e reason when we're doing this we get a Segmentation Fault.
I asume we're doing something structurally wrong ... considering this is our first PHP extension.
full code; https://github.com/afk11/secp256k1-php
After looking at your extension code, you haven't linked the actual secp256k1 lib (.so) while building your extension ( #include "secp256k1.h" does not include actual bitcoin/secp256k1 code c library ).
You need to change your config.m4 in any of the following ways
1) Add "-l/path/to/bitcoin/secp256k1/lib" to the "gcc" options.
Help: here, I am talking about once you "make install" on
bitcoin/secp256k1, some libraries will be installed to /usr/lib or
/usr/lib64 or /usr/lib/secp256k1 etc....
-lsecp256k1
// i.e. something like...
PHP_NEW_EXTENSION(secp256k1, secp256k1.c, $ext_shared,, "-lsecp256k1 -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1")
2) Or, include actual *.c files in from actual secp256k1 library
PHP_NEW_EXTENSION(secp256k1, secp256k1.c ../secp256k1/src/secp256k1.c ../secp256k1/src/others.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
I would recommend option-1

Calling token_get_all from c program

I'm trying to write a c program that can analyse PHP scripts for various reasons. I need to call token_get_all from the PHP core library, but I'm struggling mightily with it. I've compiled the php7 master branch statically and linked it to my program. I'll post a couple of code snippets as I've tried many ways of calling this function and keep running into errors of one type or another.
This snippet gets the farthest when I execute it:
int main(void)
{
zval function_name, params[1], return_value, param;
ZVAL_NEW_STR(&function_name, zend_string_init("token_get_all", strlen("token_get_all"), 1));
printf("Got here\n");
ZVAL_NEW_STR(&params[0], zend_string_init("<?php $x = 1;", strlen("<?php $x = 1;"), 1));
int ret;
printf("Calling function\n");
ret = call_user_function(CG(function_table), NULL, &function_name, &return_value, 1, params TSRMLS_CC);
printf("%i", ret);
}
When it gets to call_user_function, it segfaults. Valgrind output:
==11451== 1 errors in context 1 of 1:
==11451== Invalid read of size 8
==11451== at 0x40268C: parse_php (parser.c:73)
==11451== by 0x402730: parse_php_file (parser.c:95)
==11451== by 0x4029B2: main (parse-script-main.c:14)
==11451== Address 0x0 is not stack'd, malloc'd or (recently) free'd
parser.c:73 is the line of call_user_function.
I'll post this as well, though it may be a separate question. If I change the way I initialize function_name or my lone parameter I wind up with a different segfault. Consider this:
int main(void)
{
zval function_name, params[1], return_value, param;
ZVAL_STRING(&function_name, "token_get_all");
printf("Got here\n");
ZVAL_NEW_STR(&params[0], zend_string_init("<?php $x = 1;", strlen("<?php $x = 1;"), 1));
int ret;
printf("Calling function\n");
ret = call_user_function(CG(function_table), NULL, &function_name, &return_value, 1, params TSRMLS_CC);
printf("%i", ret);
}
This gives me another segfault on the ZVAL_STRING line:
==11481== 1 errors in context 1 of 1:
==11481== Invalid read of size 8
==11481== at 0x407DF5: _emalloc (zend_alloc.c:2376)
==11481== by 0x402559: zend_string_alloc (zend_string.h:121)
==11481== by 0x4025C2: zend_string_init (zend_string.h:157)
==11481== by 0x402639: parse_php (parser.c:65)
==11481== by 0x402747: parse_php_file (parser.c:95)
==11481== by 0x4029C9: main (parse-script-main.c:14)
==11481== Address 0x0 is not stack'd, malloc'd or (recently) free'd
Finally, here's my compiler/linker commands:
gcc -g -D_GNU_SOURCE -Iinclude -I/usr/local/include/php -I/usr/local/include/php/Zend -I/usr/local/include/php/include -I/usr/local/include/php/main -I/usr/local/include/php/ext -I/usr/local/include/php/sapi -I/usr/local/include/php/TSRM -c /path/to/parser.c -o obj/Debug/include/parser.o
g++ -Linclude -L/usr/lib/x86_64-linux-gnu -L/usr/local/lib -o bin/Debug/parse-script obj/Debug/include/parser.o obj/Debug/include/project.o obj/Debug/include/utils.o obj/Debug/parse-script-main.o -lphp7 -ldl -lc -lpthread -lgcc
I know the generated object files don't match the code above. I wrapped my problem function in "int main(void)" in the examples above.
Okay, I managed to get past this. I'll post my findings here. Basically, most of the information you find when you search on this topic (at least for me) is related to writing PHP extensions, not linking PHP into your c app and calling some of its internal functions. Here's what is currently working for me:
main.c:
#include <stdio.h>
#include <stdlib.h>
#include <sapi/embed/php_embed.h>
#include "parser.h"
int main(int, char *[]);
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stdout, "USAGE: parse-script <php-file>\n");
exit(0);
}
PHP_EMBED_START_BLOCK(argc, argv);
parse_php_file(argv[1]);
PHP_EMBED_END_BLOCK();
return 0;
}
parser.c:
#include <stdio.h>
#include "utils.h"
#include "php.h"
int parse_php(char *code)
{
zval function_name;
zval return_value;
int param_count = 1;
zval code_param;
zval *params[1];
ZVAL_STRINGL(params[0], code, strlen(code), 0);
INIT_ZVAL(function_name);
ZVAL_STRING(&function_name, "token_get_all", 0);
TSRMLS_FETCH();
if (call_user_function(CG(function_table), (zval **)NULL, &function_name, &return_value, 1, params TSRMLS_CC) == SUCCESS) {
zend_print_zval_r(&return_value, 0);
} else {
fprintf(stderr, "Error parsing PHP code.\n");
}
printf("Done\n");
}
int parse_php_file(char *file_name)
{
char *code;
code = read_file(file_name);
if (code == NULL) {
fprintf(stderr, "Could not read file: %s\n", file_name);
return 0;
}
parse_php(code);
}
The key seems to be the PHP_EMBED_START_BLOCK() and PHP_EMBED_END_BLOCK(). Wrapping my main code in those two statements made everything start working properly. Hopefully this will save someone some headaches down the road :)

Categories