从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 phpize
configuration文件
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服务器上,然后将它们作为函数运行。