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 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