从PHP运行可执行文件而不会产生shell

我需要从强制的PHP脚本上下文中调用一个可执行文件。 性能和安全性方面,最好不要在Web服务器进程和可执行文件之间调用一个shell。

当然,我在网上search,没有成功(在这样的PHP环境下)。 许多其他语言允许并清楚地logging。

唉,反引号, exec()shell_exec()passthru()system()proc_open()popen()调用一个shell。 而pcntl_fork()似乎不可用。

如何testing一个函数是否调用一个shell。

这是在Debian 6 64bit上用PHP 5.3.3-7 + squeeze15testing的。 testing代码在http://pastebin.com/y4C7MeJz

为了得到一个有意义的testing,我使用了一个技巧,就是要求执行一个不能作为可执行文件使用的shell命令。 一个很好的例子是umask 。 任何返回类似0022的函数都被称为shell。 exec()shell_exec()passthru()system()proc_open()都做了。 请参阅http://pastebin.com/RBcBz02F上的详细结果。

pcntl_fork失败

现在,回到目标:如何执行任意程序而不启动shell?

PHP的exec执行预期的数组string参数,而不是一个唯一的string。 但pcntl_fork只是停止请求,甚至没有日志。

编辑:pcntl_fork失败是因为服务器使用Apache的mod_php,请参阅http://www.php.net/manual/en/function.pcntl-fork.php#49949 。

编辑:添加popen()到@hakrebuild议之后的testing。

任何暗示赞赏。

回答你的话:

性能和安全性方面,最好不要在Web服务器进程和可执行文件之间调用一个shell。

关于表演,嗯,是的,PHP的内部叉,和壳本身分叉,所以有点沉重。 但是,你真的需要执行很多stream程来考虑这些performance问题。

关于安全性,我在这里没有看到任何问题。 PHP有escapeshellarg函数来清理参数。

在没有pcntl的情况下遇到exec的唯一真正的问题不是资源也不是安全问题:创build真正的 deamons(没有任何附加到其父,特别是Apache )是非常困难的。 我解决了这个问题,通过at我的命令双重转义之后使用:

 $arg1 = escapeshellarg($arg1); $arg2 = escapeshellarg($arg2); $command = escapeshellarg("/some/bin $arg1 $arg2 > /dev/null 2>&1 &"); exec("$command | at now -M"); 

回到你的问题,我知道以标准 (fork + exec)方式执行程序的唯一方法是使用PCNTL扩展( 如前所述 )。 无论如何,祝你好运!


为了完成我的答案,你可以创build一个exec函数,它和pcntl_fork + pcntl_exec

我做了一个经典的exec + fork的my_exec扩展,但实际上, 如果你在apache下运行这个函数我不认为它会解决你的问题 ,因为和pcntl_fork一样的行为也适用(apache2将被分叉当execv不成功时可能会出现信号捕捉等意想不到的行为)。

config.m4 phpizeconfiguration文件

 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

 #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一个使用示例

 <?php dl("my_exec.so"); $output = my_exec("/bin/ls", array("-l", "/")); var_dump($output); 

shell脚本运行这些命令,当然使用你自己的模块目录

 phpize ./configure make sudo cp modules/my_exec_extension.so /opt/local/lib/php/extensions/no-debug-non-zts-20090626/my_exec.so 

结果

 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" 

我不是C开发人员,所以我认为有更清晰的方法来实现这一点。 但是你明白了。

编号考虑尝试pcntl_exec()

你不能在没有shell的情况下启动任何程序 – shell是提供其环境设置的执行程序的包装,如PWD和PATH。

如果你希望在不启动shell的情况下运行系统命令,可以将系统命令重新编写为PHP模块,并将它们安装到PHP服务器上,然后将它们作为函数运行。