This patch is based on http://kyberdigi.cz/projects/execdir/ but instead trying to sanitize the exec command string inside PHP this job is handled by bash and a special restricted PHP-mode which strips off all path informations. This patch depends on: - bash restricted php mode patch https://manuel.mausz.at/coding/patches/php/bash-4.2-restricted-php.patch No warranty! diff -Naur php-5.6.30.orig/ext/standard/exec.c php-5.6.30/ext/standard/exec.c --- php-5.6.30.orig/ext/standard/exec.c 2017-01-19 01:17:47.000000000 +0100 +++ php-5.6.30/ext/standard/exec.c 2017-02-16 18:05:58.948877544 +0100 @@ -105,21 +105,30 @@ FILE *fp; char *buf; int l = 0, pclose_return; - char *b, *d=NULL; + char *cmd_p, *b, *d=NULL; php_stream *stream; size_t buflen, bufl = 0; #if PHP_SIGCHILD void (*sig_handler)() = NULL; #endif + if (PG(exec_dir) && strlen(PG(exec_dir))) { + cmd_p = php_escape_shell_arg(cmd); + spprintf(&d, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), cmd_p); + efree(cmd_p); + cmd_p = d; + } else { + cmd_p = cmd; + } + #if PHP_SIGCHILD sig_handler = signal (SIGCHLD, SIG_DFL); #endif #ifdef PHP_WIN32 - fp = VCWD_POPEN(cmd, "rb"); + fp = VCWD_POPEN(cmd_p, "rb"); #else - fp = VCWD_POPEN(cmd, "r"); + fp = VCWD_POPEN(cmd_p, "r"); #endif if (!fp) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to fork [%s]", cmd); @@ -528,7 +537,7 @@ { FILE *in; size_t total_readbytes; - char *command; + char *command, *command_p; int command_len; char *ret; php_stream *stream; @@ -537,14 +546,24 @@ return; } + if (PG(exec_dir) && strlen(PG(exec_dir))) { + char *d = php_escape_shell_arg(command); + spprintf(&command_p, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), d); + efree(d); + } else { + command_p = estrdup(command); + } + #ifdef PHP_WIN32 - if ((in=VCWD_POPEN(command, "rt"))==NULL) { + if ((in=VCWD_POPEN(command_p, "rt"))==NULL) { #else - if ((in=VCWD_POPEN(command, "r"))==NULL) { + if ((in=VCWD_POPEN(command_p, "r"))==NULL) { #endif php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to execute '%s'", command); + efree(command_p); RETURN_FALSE; } + efree(command_p); stream = php_stream_fopen_from_pipe(in, "rb"); total_readbytes = php_stream_copy_to_mem(stream, &ret, PHP_STREAM_COPY_ALL, 0); diff -Naur php-5.6.30.orig/ext/standard/file.c php-5.6.30/ext/standard/file.c --- php-5.6.30.orig/ext/standard/file.c 2017-01-19 01:17:47.000000000 +0100 +++ php-5.6.30/ext/standard/file.c 2017-02-16 18:05:58.949877533 +0100 @@ -944,7 +944,16 @@ } #endif - fp = VCWD_POPEN(command, posix_mode); + if (PG(exec_dir) && strlen(PG(exec_dir))) { + char *command_p, *tmp; + command_p = php_escape_shell_arg(command); + spprintf(&tmp, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), command_p); + fp = VCWD_POPEN(tmp, posix_mode); + efree(tmp); + efree(command_p); + } else { + fp = VCWD_POPEN(command, posix_mode); + } if (!fp) { php_error_docref2(NULL TSRMLS_CC, command, posix_mode, E_WARNING, "%s", strerror(errno)); efree(posix_mode); diff -Naur php-5.6.30.orig/ext/standard/filestat.c php-5.6.30/ext/standard/filestat.c --- php-5.6.30.orig/ext/standard/filestat.c 2017-01-19 01:17:47.000000000 +0100 +++ php-5.6.30/ext/standard/filestat.c 2017-02-16 18:06:23.005603384 +0100 @@ -858,13 +858,14 @@ "size", "atime", "mtime", "ctime", "blksize", "blocks" }; const char *local; + char local_safe[MAXPATHLEN]; php_stream_wrapper *wrapper; if (!filename_length) { RETURN_FALSE; } - if ((wrapper = php_stream_locate_url_wrapper(filename, &local, 0 TSRMLS_CC)) == &php_plain_files_wrapper && php_check_open_basedir(local TSRMLS_CC)) { + if ((wrapper = php_stream_locate_url_wrapper(filename, &local, 0 TSRMLS_CC)) == &php_plain_files_wrapper && type != FS_IS_X && php_check_open_basedir(local TSRMLS_CC)) { RETURN_FALSE; } @@ -889,6 +890,19 @@ #endif #ifdef X_OK case FS_IS_X: + if (PG(exec_dir) && strlen(PG(exec_dir))) { + if (strstr(local, "..")) { + RETURN_FALSE; + } else { + char *b = strrchr(local, PHP_DIR_SEPARATOR); + if (b) { + snprintf(local_safe, MAXPATHLEN, "%s%s", PG(exec_dir), b); + } else { + snprintf(local_safe, MAXPATHLEN, "%s%c%s", PG(exec_dir), PHP_DIR_SEPARATOR, local); + } + local = (char *)&local_safe; + } + } RETURN_BOOL(VCWD_ACCESS(local, X_OK) == 0); break; #endif diff -Naur php-5.6.30.orig/ext/standard/proc_open.c php-5.6.30/ext/standard/proc_open.c --- php-5.6.30.orig/ext/standard/proc_open.c 2017-01-19 01:17:47.000000000 +0100 +++ php-5.6.30/ext/standard/proc_open.c 2017-02-16 18:05:58.950877521 +0100 @@ -521,6 +521,14 @@ command_len = strlen(command); + if (PG(exec_dir) && strlen(PG(exec_dir))) { + if (!environment) { + MAKE_STD_ZVAL(environment); + array_init_size(environment, 1); + } + add_assoc_string(environment, "PATH", PG(exec_dir), 1); + } + if (environment) { env = _php_array_to_envp(environment, is_persistent TSRMLS_CC); } else { @@ -880,10 +888,14 @@ php_ignore_value(chdir(cwd)); } - if (env.envarray) { - execle("/bin/sh", "sh", "-c", command, NULL, env.envarray); + if (PG(exec_dir) && strlen(PG(exec_dir))) { + execle("/bin/bash", "bash", "--php", "-rc", command, NULL, env.envarray); } else { - execl("/bin/sh", "sh", "-c", command, NULL); + if (env.envarray) { + execle("/bin/sh", "sh", "-c", command, NULL, env.envarray); + } else { + execl("/bin/sh", "sh", "-c", command, NULL); + } } _exit(127); diff -Naur php-5.6.30.orig/main/main.c php-5.6.30/main/main.c --- php-5.6.30.orig/main/main.c 2017-01-19 01:17:47.000000000 +0100 +++ php-5.6.30/main/main.c 2017-02-16 18:05:58.951877510 +0100 @@ -643,6 +643,7 @@ #ifdef PHP_WIN32 STD_PHP_INI_BOOLEAN("windows.show_crt_warning", "0", PHP_INI_ALL, OnUpdateBool, windows_show_crt_warning, php_core_globals, core_globals) #endif + STD_PHP_INI_ENTRY("exec_dir", NULL, PHP_INI_SYSTEM, OnUpdateString, exec_dir, php_core_globals, core_globals) PHP_INI_END() /* }}} */ diff -Naur php-5.6.30.orig/main/php_globals.h php-5.6.30/main/php_globals.h --- php-5.6.30.orig/main/php_globals.h 2017-01-19 01:17:47.000000000 +0100 +++ php-5.6.30/main/php_globals.h 2017-02-16 18:05:58.951877510 +0100 @@ -167,6 +167,7 @@ #ifdef PHP_WIN32 zend_bool windows_show_crt_warning; #endif + char *exec_dir; }; diff -Naur php-5.6.30.orig/php.ini-development php-5.6.30/php.ini-development --- php-5.6.30.orig/php.ini-development 2017-01-19 01:17:47.000000000 +0100 +++ php-5.6.30/php.ini-development 2017-02-16 18:05:58.952877499 +0100 @@ -297,6 +297,10 @@ ; http://php.net/open-basedir ;open_basedir = +; Only executables located in the exec_dir will be allowed to be executed +; via the exec family of functions. +exec_dir = + ; This directive allows you to disable certain functions for security reasons. ; It receives a comma-delimited list of function names. ; http://php.net/disable-functions diff -Naur php-5.6.30.orig/php.ini-production php-5.6.30/php.ini-production --- php-5.6.30.orig/php.ini-production 2017-01-19 01:17:47.000000000 +0100 +++ php-5.6.30/php.ini-production 2017-02-16 18:05:58.953877487 +0100 @@ -297,6 +297,10 @@ ; http://php.net/open-basedir ;open_basedir = +; Only executables located in the exec_dir will be allowed to be executed +; via the exec family of functions. +exec_dir = + ; This directive allows you to disable certain functions for security reasons. ; It receives a comma-delimited list of function names. ; http://php.net/disable-functions