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