Run executable from php without spawning a shell

后端 未结 2 1597
梦毁少年i
梦毁少年i 2020-12-29 18:30

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 a

相关标签:
2条回答
  • 2020-12-29 19:11

    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.

    0 讨论(0)
  • 2020-12-29 19:15

    Id consider trying pcntl_exec()

    0 讨论(0)
提交回复
热议问题