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!
--- a/ext/standard/exec.c	2021-01-05 14:54:54.000000000 +0100
+++ b/ext/standard/exec.c	2021-01-08 11:05:40.001529198 +0100
@@ -116,21 +116,30 @@
 	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_esc = php_escape_shell_arg(cmd);
+		spprintf(&d, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
+		zend_string_release(cmd_esc);
+		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);
@@ -513,7 +522,7 @@
 PHP_FUNCTION(shell_exec)
 {
 	FILE *in;
-	char *command;
+	char *command, *command_p;
 	size_t command_len;
 	zend_string *ret;
 	php_stream *stream;
@@ -531,14 +540,24 @@
 		RETURN_THROWS();
 	}
 
+	if (PG(exec_dir) && strlen(PG(exec_dir))) {
+		zend_string *cmd_esc = php_escape_shell_arg(command);
+		spprintf(&command_p, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
+		zend_string_release(cmd_esc);
+	} 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);
--- a/ext/standard/file.c	2021-01-05 14:54:54.000000000 +0100
+++ b/ext/standard/file.c	2021-01-08 11:05:40.002529191 +0100
@@ -955,7 +955,16 @@
 		RETURN_THROWS();
 	}
 
-	fp = VCWD_POPEN(command, posix_mode);
+	if (PG(exec_dir) && strlen(PG(exec_dir))) {
+		zend_string *cmd_esc = php_escape_shell_arg(command);
+		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);
+	} else {
+		fp = VCWD_POPEN(command, posix_mode);
+	}
 	if (!fp) {
 		php_error_docref2(NULL, command, posix_mode, E_WARNING, "%s", strerror(errno));
 		efree(posix_mode);
--- a/ext/standard/filestat.c	2021-01-05 14:54:54.000000000 +0100
+++ b/ext/standard/filestat.c	2021-01-08 11:05:40.003529183 +0100
@@ -734,6 +734,7 @@
 	php_stream_statbuf ssb;
 	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;
 		}
 
@@ -769,7 +770,22 @@
 #endif
 #ifdef X_OK
 				case FS_IS_X:
-					RETURN_BOOL(VCWD_ACCESS(local, X_OK) == 0);
+					bool xok = (VCWD_ACCESS(local, X_OK) == 0);
+					if (!xok && 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;
+						}
+						xok = (VCWD_ACCESS(local, X_OK) == 0);
+					}
+					RETURN_BOOL(xok);
 					break;
 #endif
 			}
--- a/ext/standard/proc_open.c	2021-01-05 14:54:54.000000000 +0100
+++ b/ext/standard/proc_open.c	2021-01-08 11:05:40.004529176 +0100
@@ -618,7 +618,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);
@@ -1081,6 +1091,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);
 	}
@@ -1207,10 +1226,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);
+				}
 			}
 		}
 
--- a/main/main.c	2021-01-08 10:57:59.719887573 +0100
+++ b/main/main.c	2021-01-08 11:05:40.005529169 +0100
@@ -749,6 +749,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()
 /* }}} */
 
--- a/main/php_globals.h	2021-01-05 14:54:54.000000000 +0100
+++ b/main/php_globals.h	2021-01-08 11:05:40.005529169 +0100
@@ -165,6 +165,8 @@
 	char *syslog_ident;
 	bool have_called_openlog;
 	zend_long syslog_filter;
+
+	char *exec_dir;
 };
 
 
--- a/php.ini-development	2021-01-05 14:54:54.000000000 +0100
+++ b/php.ini-development	2021-01-08 11:05:40.006529161 +0100
@@ -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
--- a/php.ini-production	2021-01-05 14:54:54.000000000 +0100
+++ b/php.ini-production	2021-01-08 11:05:40.007529154 +0100
@@ -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