从php运行可执行文件而无需生成shell [英] Run executable from php without spawning a shell

查看:94
本文介绍了从php运行可执行文件而无需生成shell的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要从PHP脚本的强制上下文中调用可执行文件.从性能和安全角度考虑,最好不要在Web服务器进程和可执行文件之间完全调用Shell.

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.

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

Of course I searched the web, without success (in such a PHP context). Many other languages allow that and document it clearly.

A,反引号,exec()shell_exec()passthru()system()proc_open()popen()调用外壳程序. 而且pcntl_fork()似乎不可用.

Alas, backticks, exec(), shell_exec(), passthru(), system(), proc_open(), popen() call a shell. And pcntl_fork() seems unavailable.

这已在使用PHP 5.3.3-7 + squeeze15的Debian 6 64bit上进行了测试. 在 http://pastebin.com/y4C7MeJz

This was tested on a Debian 6 64bit with PHP 5.3.3-7+squeeze15 . Test code on http://pastebin.com/y4C7MeJz

为了获得有意义的测试,我使用了一个技巧,要求执行一个不能同时作为可执行文件使用的shell命令.一个很好的例子是umask.任何返回诸如0022之类的函数都绝对称为shell. exec()shell_exec()passthru()system()proc_open()都可以. 在 http://pastebin.com/RBcBz02F 上查看详细结果.

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 .

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

Now, back the the goal : how to execute arbitrary program without launching a shell ?

Php的exec按预期采用字符串args数组而不是唯一字符串.但是pcntl_fork只是停止了请求,甚至没有日志.

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.

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

pcntl_fork failure is because the server uses Apache's mod_php, see http://www.php.net/manual/en/function.pcntl-fork.php#49949 .

根据@hakre建议,在测试中添加了popen().

added popen() to the tests, following @hakre suggestion.

任何提示表示赞赏.

推荐答案

回答你的句子:

从性能和安全角度考虑,最好不要在以下位置调用shell 都是在Web服务器进程和可执行文件之间.

Both performance and security wise it's better not to call a shell at all between web server process and executable.

关于性能,好吧,是的,php内部分叉,而shell本身也分叉,所以有点沉重.但是您确实需要执行很多流程来考虑这些性能问题.

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.

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

About security, I do not see any issue here. PHP has the escapeshellarg function to sanitize arguments.

我在没有pcntl的情况下遇到exec的唯一实际问题既不是资源也不是安全问题:创建 real 守护进程确实很困难(没有其父级的任何附件,尤其是 Apache ).在两次转义命令后,我使用at解决了这个问题:

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

要回到您的问题,我知道以 standard (fork + exec)方式执行程序的唯一方法是使用

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!

要完成我的答案,您可以自己创建一个exec函数,该函数执行与pcntl_fork + pcntl_exec相同的功能.

To complete my answer, you can create an exec function yourself that does the same thing as pcntl_fork+pcntl_exec.

我做了一个my_exec扩展程序,它具有经典的exec + fork功能,但实际上,如果您在apache下运行此功能,我认为它不会解决您的问题,因为相同会出现pcntl_fork这样的行为(apache2将被派生,并且当execv不成功时,可能会出现信号捕获等意外行为).

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

test.php 使用示例

<?php

dl("my_exec.so");

$output = my_exec("/bin/ls", array("-l", "/"));
var_dump($output);

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

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

结果

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开发人员,所以我认为有更清洁的方法可以实现这一目标.但是你明白了.

I am not a C dev, so I think there are cleaner ways to achieve this. But you get the idea.

这篇关于从php运行可执行文件而无需生成shell的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆