从php运行可执行文件而不生成shell

从php运行可执行文件而不生成shell,php,performance,security,shell,Php,Performance,Security,Shell,我需要从PHP脚本的强制上下文调用一个可执行文件。在性能和安全方面,最好不要在web服务器进程和可执行文件之间调用shell 当然,我搜索了web,但没有成功(在这样的PHP上下文中)。许多其他语言都允许这样做,并清楚地记录下来 唉,反循环,exec(),shell\u exec(),passthru(),system(),proc\u open(),popen()调用shell。 而且,pcntl\u fork()似乎不可用 如何测试函数是否调用shell。 这是用PHP5.3.3-7+squ

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

当然,我搜索了web,但没有成功(在这样的PHP上下文中)。许多其他语言都允许这样做,并清楚地记录下来

唉,反循环,
exec()
shell\u exec()
passthru()
system()
proc\u open()
popen()
调用shell。 而且,
pcntl\u fork()
似乎不可用

如何测试函数是否调用shell。 这是用PHP5.3.3-7+squeeze15在Debian664位上测试的。 测试代码

为了得到一个有意义的测试,我使用了一个技巧,那就是要求执行一个不能作为可执行文件使用的shell命令。一个很好的例子是
umask
。任何返回类似0022的函数都被称为shell
exec()
shell\u exec()
passthru()
system()
proc\u open()。
请参阅上的详细结果

pcntl_分叉失败 现在,回到目标:如何在不启动shell的情况下执行任意程序

Php的exec按预期使用字符串参数数组,而不是唯一的字符串。但是pcntl_fork只是在没有日志的情况下停止请求

编辑:pcntl_fork失败是因为服务器使用Apache的mod_php,请参阅

编辑:在@hakre建议之后,将
popen()
添加到测试中


任何提示感谢。

我考虑尝试< /P> < P>来回答你的句子:

在性能和安全方面,最好不要同时调用shell 所有这些都在web服务器进程和可执行文件之间

关于性能,是的,php内部是分叉的,shell本身也是分叉的,所以这有点沉重。但是你确实需要执行很多过程来考虑这些性能问题。 关于安全问题,我认为这里没有任何问题。PHP具有清理参数的功能

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

$arg1 = escapeshellarg($arg1);
$arg2 = escapeshellarg($arg2);
$command = escapeshellarg("/some/bin $arg1 $arg2 > /dev/null 2>&1 &");
exec("$command | at now -M");
回到您的问题,我知道的以标准(fork+exec)方式执行程序的唯一方法是使用扩展(如前所述)。不管怎样,祝你好运


要完成我的回答,您可以自己创建一个
exec
函数,它执行与
pcntl\u fork
+
pcntl\u exec
相同的操作

我做了一个
my_-exec
扩展,它做了一个经典的exec+fork,但是实际上,如果你在apache下运行这个函数,我不认为它能解决你的问题,因为与
pcntl_-fork
相同的行为将适用(apache2将分叉,当
execv
未成功时,可能会出现信号捕捉等意外行为)

config.m4配置文件
phpize

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);
}
结果

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

在PHP7.4+中,如果
cmd
作为数组传递,则直接打开进程

从PHP7.4.0开始,cmd可以作为命令参数数组传递。在这种情况下,进程将直接打开(不经过shell),PHP将处理任何必要的参数转义

所以这个例子:

<?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);

当我第一次发现PHP似乎不提供任何机制来调用外部程序而不通过shell传递命令行时,我感到震惊和惊讶。这是一种通用的、流行的编程语言,具有类似
exec()
system()的命令
有这个缺陷,而且它从未被修复过吗?真的吗?但是……我想这是真的。@celada:你必须认识到PHP只是大量标准glib/libc函数的包装器。PHP本身不能做glib/libc自己不能做的事情,其中一个是通过shell执行另一个进程。@PH.t几十年的模式是:call fork,然后子进程调用exec。PHP在这里执行的是一个shell,而不是预期的程序。如果您实际需要shell构造,shell有时可能很有用。但是如果您只想运行一个程序,shell将只用于将一个以空格分隔的参数字符串拆分为一个数组,从而在进程中引入安全漏洞ess,然后再次使用fork+exec。直接传递所需的参数数组并调用exec更简单、更安全。有关Perl中的等效情况,请参阅例如..@MathewFoscarini。这不是真的,如果正确分离,PHP可以执行任意多个shell。有两件事要做:将进程放在后台并重定向标准输出更多细节请参见。@StéphaneGourichon测试shell的另一种方法:
php-r'系统(“睡眠1000”)
然后在另一个终端
pstree | grep sleep
中。此示例显示了插入php和sleep之间的shell:
-konsole-+-bash--php--sh--sleep
读取此php安装中不可用的pastebin:
PCNTL函数。
确实:“当PHP用作Apache模块时,不可能使用函数‘pcntl_fork’。”不管文档中写了什么,pcntl_fork()当PHP是Apache模块时可以使用。但是它会分叉整个Apache服务器。感谢您提供了这一信息性的答案。它涵盖了关于PHP的两个主题:第一,接受分叉一个无用的外壳并解决安全问题(escapeshellargs),第二,如何创建守护进程(这部分避开了所问的问题)。请参阅George Cummins的《赏金》。任何编程语言中的任何shell exec都存在安全问题,因为大多数shell(sh、ash、bash、ksh、csh、tcsh等)略有不同,而
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"
<?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);
cmd_string:
bash
php
sh
ps

cmd_array:
bash
php
ps