Hi all,

Please find a patch attached which makes it possible to have secure
suexec CGI / FastCGI PHP scripts without using safe_mode (note that this
configuration is currently widely used, for example by shared web hosts,
but there is no way to make it secure with an unpatched PHP version).

For further discussion of the security flaw this allows administrators
to correct, please see my Bugtraq post:
    * http://www.securityfocus.com/archive/1/500850

I have made the patch against 5.2.8, but cgi_main.c hasn't changed much
on any branch since then, so if it doesn't apply cleanly to the HEAD, it
won't take much effort to make it apply.

I realise that this adds extra INI variables for security protection
which is unfashionable of late, but it is really a different class of
security protection compared to safe mode et. al. This patch merely
allows admins to close a security hole in which script is initially
executed when calling from suexec, rather than trying to restrict what
the script can do once it is run. As such, this patch makes it easier to
safely rely on the operating system to provide security, and reduces the
reliance on safe mode style features.

I think having the ability to safely run PHP from suexec FastCGI
environments is very important for many key applications, such as shared
web-hosting, as these environments need the combination of scripts
running under individual user accounts, and high performance. Many
shared hosting environments already use this type of setup, albeit
insecurely.

Best regards,
Andrew Miller




diff -rbud ./php-5.2.8-orig/sapi/cgi/cgi_main.c ./php-5.2.8/sapi/cgi/cgi_main.c
--- ./php-5.2.8-orig/sapi/cgi/cgi_main.c	2009-02-10 21:37:09.000000000 +1300
+++ ./php-5.2.8/sapi/cgi/cgi_main.c	2009-02-11 00:07:51.000000000 +1300
@@ -67,6 +67,9 @@
 #include <fcntl.h>
 #include "win32/php_registry.h"
 #endif
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
 
 #ifdef __riscos__
 #include <unixlib/local.h>
@@ -170,6 +173,10 @@
 	zend_bool impersonate;
 # endif
 #endif
+#ifdef HAVE_PWD_H
+    char* suexec_base_dir;
+    char* suexec_user_dir;
+#endif
 } php_cgi_globals_struct;
 
 #ifdef ZTS
@@ -1232,6 +1239,10 @@
 	STD_PHP_INI_ENTRY("fastcgi.impersonate",     "0",  PHP_INI_SYSTEM, OnUpdateBool,   impersonate, php_cgi_globals_struct, php_cgi_globals)
 # endif
 #endif
+#ifdef HAVE_PWD_H
+    STD_PHP_INI_ENTRY("cgi.suexec_base_dir",     NULL, PHP_INI_SYSTEM, OnUpdateString, suexec_base_dir, php_cgi_globals_struct, php_cgi_globals)
+    STD_PHP_INI_ENTRY("cgi.suexec_user_dir",     NULL, PHP_INI_SYSTEM, OnUpdateString, suexec_user_dir, php_cgi_globals_struct, php_cgi_globals)
+#endif
 PHP_INI_END()
 
 /* {{{ php_cgi_globals_ctor
@@ -1254,6 +1265,10 @@
 	php_cgi_globals->impersonate = 0;
 # endif
 #endif
+#ifdef HAVE_PWD_H
+    php_cgi_globals->suexec_base_dir = NULL;
+    php_cgi_globals->suexec_user_dir = NULL;
+#endif
 }
 /* }}} */
 
@@ -1708,6 +1723,10 @@
 #if PHP_FASTCGI
 			&& !fastcgi
 #endif
+#ifdef HAVE_PWD_H
+            && CGIG(suexec_base_dir) == NULL
+            && CGIG(suexec_user_dir) == NULL
+#endif
 		) {
 			while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0)) != -1) {
 				switch (c) {
@@ -1884,6 +1903,10 @@
 #if PHP_FASTCGI
 			|| fastcgi
 #endif
+#ifdef HAVE_PWD_H
+            || CGIG(suexec_base_dir) != NULL
+            || CGIG(suexec_user_dir) != NULL
+#endif
 		)
 		{
 			file_handle.type = ZEND_HANDLE_FILENAME;
@@ -1922,9 +1945,49 @@
 		*/
 		retval = FAILURE;
 		if (cgi || SG(request_info).path_translated) {
+#ifdef HAVE_PWD_H
+            zend_bool path_ok = !(CGIG(suexec_base_dir) ||
+                                  CGIG(suexec_user_dir));
+            if (!path_ok && SG(request_info).path_translated)
+            {
+                struct stat statbuf;
+                char *real_path = tsrm_realpath(SG(request_info).path_translated, NULL TSRMLS_CC);
+
+                virtual_stat(SG(request_info).path_translated, &statbuf TSRMLS_CC);
+                /* Only execute if the script is owned by the current user,
+                 * the user execute bit is set, and it is not group or world
+                 * writable.
+                 */
+                if (statbuf.st_uid == geteuid() &&
+                    (statbuf.st_mode & 0100) == 0100 &&
+                    (statbuf.st_mode & 022) == 0) {
+                    if (CGIG(suexec_base_dir) && !strncmp(real_path, CGIG(suexec_base_dir), strlen(CGIG(suexec_base_dir)))) {
+                        path_ok = 1;
+                    }
+                    if (!path_ok && CGIG(suexec_user_dir)) {
+                        struct passwd* pw = getpwuid(geteuid());
+                        size_t len = strlen(pw->pw_dir) + 1 + strlen(CGIG(suexec_user_dir)) + 2;
+                        char * user_dir = malloc(len);
+                        strcpy(user_dir, pw->pw_dir);
+                        strlcat(user_dir, "/", len);
+                        strlcat(user_dir, CGIG(suexec_user_dir), len);
+                        strlcat(user_dir, "/", len);
+                        if (!strncmp(real_path, user_dir, len - 1))
+                            path_ok = 1;
+                        free(user_dir);
+                    }
+                    free(real_path);
+                }
+            }
+
+            if (path_ok) {
+#endif
 			if (!php_check_open_basedir(SG(request_info).path_translated TSRMLS_CC)) {
 				retval = php_fopen_primary_script(&file_handle TSRMLS_CC);
 			}
+#ifdef HAVE_PWD_H
+            }
+#endif
 		}
 		/* 
 			if we are unable to open path_translated and we are not




-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to