Download | Plain Text | Line Numbers
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 a/ext/standard/exec.c b/ext/standard/exec.c
--- a/ext/standard/exec.c 2024-09-24 17:59:52.000000000 +0200
+++ b/ext/standard/exec.c 2024-09-27 14:57:40.554136412 +0200
@@ -114,21 +114,32 @@
FILE *fp;
char *buf;
int 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))) {
+ zend_string *cmd_str = zend_string_init(cmd, strlen(cmd), 0);
+ zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
+ spprintf(&d, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
+ zend_string_release(cmd_esc);
+ zend_string_release(cmd_str);
+ 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, E_WARNING, "Unable to fork [%s]", cmd);
@@ -508,7 +519,7 @@
PHP_FUNCTION(shell_exec)
{
FILE *in;
- char *command;
+ char *command, *command_p;
size_t command_len;
zend_string *ret;
php_stream *stream;
@@ -522,14 +533,26 @@
RETURN_THROWS();
}
+ if (PG(exec_dir) && strlen(PG(exec_dir))) {
+ zend_string *cmd_str = zend_string_init(command, command_len, 0);
+ zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
+ spprintf(&command_p, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
+ zend_string_release(cmd_esc);
+ zend_string_release(cmd_str);
+ } 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, E_WARNING, "Unable to execute '%s'", command);
+ efree(command_p);
RETURN_FALSE;
}
+ efree(command_p);
stream = php_stream_fopen_from_pipe(in, "rb");
ret = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
diff -Naur a/ext/standard/file.c b/ext/standard/file.c
--- a/ext/standard/file.c 2024-09-24 17:59:52.000000000 +0200
+++ b/ext/standard/file.c 2024-09-27 14:57:40.555136404 +0200
@@ -28,6 +28,7 @@
#include "ext/standard/basic_functions.h"
#include "php_ini.h"
#include "zend_smart_str.h"
+#include "ext/standard/exec.h"
#include <stdio.h>
#include <stdlib.h>
@@ -813,7 +814,18 @@
RETURN_THROWS();
}
- fp = VCWD_POPEN(command, posix_mode);
+ if (PG(exec_dir) && strlen(PG(exec_dir))) {
+ zend_string *cmd_str = zend_string_init(command, command_len, 0);
+ zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
+ char *tmp;
+ spprintf(&tmp, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
+ fp = VCWD_POPEN(tmp, posix_mode);
+ efree(tmp);
+ zend_string_release(cmd_esc);
+ zend_string_release(cmd_str);
+ } else {
+ fp = VCWD_POPEN(command, posix_mode);
+ }
if (!fp) {
php_error_docref2(NULL, command, posix_mode, E_WARNING, "%s", strerror(errno));
efree(posix_mode);
diff -Naur a/ext/standard/filestat.c b/ext/standard/filestat.c
--- a/ext/standard/filestat.c 2024-09-24 17:59:52.000000000 +0200
+++ b/ext/standard/filestat.c 2024-09-27 14:57:40.556136395 +0200
@@ -734,6 +734,7 @@
zend_stat_t *stat_sb = &ssb.sb;
int flags = 0, rmask=S_IROTH, wmask=S_IWOTH, xmask=S_IXOTH; /* access rights defaults to other */
const char *local = NULL;
+ char local_safe[MAXPATHLEN];
php_stream_wrapper *wrapper = NULL;
if (IS_ACCESS_CHECK(type)) {
@@ -745,7 +746,7 @@
}
if ((wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(filename), &local, 0)) == &php_plain_files_wrapper
- && php_check_open_basedir(local)) {
+ && type != FS_IS_X && php_check_open_basedir(local)) {
RETURN_FALSE;
}
@@ -775,9 +776,25 @@
break;
#endif
#ifdef X_OK
- case FS_IS_X:
- RETURN_BOOL(VCWD_ACCESS(file_path_to_check, X_OK) == 0);
+ case FS_IS_X: {
+ bool xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
+ if (!xok && PG(exec_dir) && strlen(PG(exec_dir))) {
+ if (strstr(file_path_to_check, "..")) {
+ RETURN_FALSE;
+ } else {
+ char *b = strrchr(file_path_to_check, 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, file_path_to_check);
+ }
+ file_path_to_check = (char *)&local_safe;
+ }
+ xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
+ }
+ RETURN_BOOL(xok);
break;
+ }
#endif
}
}
diff -Naur a/ext/standard/proc_open.c b/ext/standard/proc_open.c
--- a/ext/standard/proc_open.c 2024-09-24 17:59:52.000000000 +0200
+++ b/ext/standard/proc_open.c 2024-09-27 14:58:20.151810732 +0200
@@ -728,7 +728,17 @@
zend_string *command = NULL;
int i = 0;
- *argv = safe_emalloc(sizeof(char *), num_elems + 1, 0);
+ *argv = safe_emalloc(sizeof(char *), num_elems + 1 + 5, 0);
+
+ if (PG(exec_dir) && strlen(PG(exec_dir))) {
+ command = zend_string_init("/bin/bash", sizeof("/bin/bash") - 1, 0);
+ (*argv)[i++] = estrdup("/bin/bash");
+ (*argv)[i++] = estrdup("--php");
+ (*argv)[i++] = estrdup("-rc");
+ (*argv)[i++] = estrdup("exec \"$@\"");
+ (*argv)[i++] = estrdup("bash");
+ (*argv)[i] = NULL;
+ }
ZEND_HASH_FOREACH_VAL(array, arg_zv) {
zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1);
@@ -1222,6 +1232,15 @@
}
#endif
+ zval envpath;
+ if (PG(exec_dir) && strlen(PG(exec_dir))) {
+ if (!environment || zend_hash_num_elements(Z_ARRVAL_P(environment)) == 0) {
+ array_init_size(&envpath, 1);
+ environment = &envpath;
+ }
+ add_assoc_string(environment, "PATH", PG(exec_dir));
+ }
+
if (environment) {
env = _php_array_to_envp(environment);
}
@@ -1347,9 +1366,15 @@
if (argv) {
r = posix_spawnp(&child, ZSTR_VAL(command_str), &factions, NULL, argv, (env.envarray ? env.envarray : environ));
} else {
- r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
- (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
- env.envarray ? env.envarray : environ);
+ if (PG(exec_dir) && strlen(PG(exec_dir))) {
+ r = posix_spawn(&child, "/bin/bash" , &factions, NULL,
+ (char * const[]) {"bash", "--php", "-rc", ZSTR_VAL(command_str), NULL},
+ env.envarray ? env.envarray : environ);
+ } else {
+ r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
+ (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
+ env.envarray ? env.envarray : environ);
+ }
}
posix_spawn_file_actions_destroy(&factions);
if (r != 0) {
@@ -1382,10 +1407,14 @@
}
execvp(ZSTR_VAL(command_str), argv);
} else {
- if (env.envarray) {
- execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
+ if (PG(exec_dir) && strlen(PG(exec_dir))) {
+ execle("/bin/bash", "bash", "--php", "-rc", ZSTR_VAL(command_str), NULL, env.envarray);
} else {
- execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
+ if (env.envarray) {
+ execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
+ } else {
+ execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
+ }
}
}
diff -Naur a/main/main.c b/main/main.c
--- a/main/main.c 2024-09-27 14:37:57.841234655 +0200
+++ b/main/main.c 2024-09-27 14:57:40.557136387 +0200
@@ -824,6 +824,7 @@
STD_PHP_INI_ENTRY("syslog.facility", "LOG_USER", PHP_INI_SYSTEM, OnSetFacility, syslog_facility, php_core_globals, core_globals)
STD_PHP_INI_ENTRY("syslog.ident", "php", PHP_INI_SYSTEM, OnUpdateString, syslog_ident, php_core_globals, core_globals)
STD_PHP_INI_ENTRY("syslog.filter", "no-ctrl", PHP_INI_ALL, OnSetLogFilter, syslog_filter, php_core_globals, core_globals)
+ STD_PHP_INI_ENTRY("exec_dir", NULL, PHP_INI_SYSTEM, OnUpdateString, exec_dir, php_core_globals, core_globals)
PHP_INI_END()
/* }}} */
diff -Naur a/main/php_globals.h b/main/php_globals.h
--- a/main/php_globals.h 2024-09-24 17:59:52.000000000 +0200
+++ b/main/php_globals.h 2024-09-27 14:57:40.557136387 +0200
@@ -172,6 +172,8 @@
char *syslog_ident;
zend_long syslog_filter;
zend_long error_log_mode;
+
+ char *exec_dir;
};
diff -Naur a/php.ini-development b/php.ini-development
--- a/php.ini-development 2024-09-24 17:59:52.000000000 +0200
+++ b/php.ini-development 2024-09-27 14:57:40.558136379 +0200
@@ -317,6 +317,10 @@
; https://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.
; It receives a comma-delimited list of function names.
; https://php.net/disable-functions
diff -Naur a/php.ini-production b/php.ini-production
--- a/php.ini-production 2024-09-24 17:59:52.000000000 +0200
+++ b/php.ini-production 2024-09-27 14:57:40.559136371 +0200
@@ -317,6 +317,10 @@
; https://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.
; It receives a comma-delimited list of function names.
; https://php.net/disable-functions