Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:43013 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 50269 invoked from network); 11 Feb 2009 22:24:36 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 11 Feb 2009 22:24:36 -0000 Authentication-Results: pb1.pair.com header.from=php-453@amxl.com; sender-id=unknown Authentication-Results: pb1.pair.com smtp.mail=php-453@amxl.com; spf=permerror; sender-id=unknown Received-SPF: error (pb1.pair.com: domain amxl.com from 130.216.12.34 cause and error) X-PHP-List-Original-Sender: php-453@amxl.com X-Host-Fingerprint: 130.216.12.34 larry.its.auckland.ac.nz Linux 2.5 (sometimes 2.4) (4) Received: from [130.216.12.34] ([130.216.12.34:42853] helo=mailhost.auckland.ac.nz) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id AA/59-12172-32053994 for ; Wed, 11 Feb 2009 17:24:36 -0500 Received: from localhost (localhost.localdomain [127.0.0.1]) by mailhost.auckland.ac.nz (Postfix) with ESMTP id 8B35B1AC71 for ; Thu, 12 Feb 2009 11:24:32 +1300 (NZDT) X-Virus-Scanned: by amavisd-new at mailhost.auckland.ac.nz Received: from mailhost.auckland.ac.nz ([127.0.0.1]) by localhost (larry.its.auckland.ac.nz [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id O24PHg8QvOzY for ; Thu, 12 Feb 2009 11:24:32 +1300 (NZDT) Received: from [192.168.1.100] (203-211-89-87.ue.woosh.co.nz [203.211.89.87]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by mailhost.auckland.ac.nz (Postfix) with ESMTP id 34E461AC79 for ; Thu, 12 Feb 2009 11:24:30 +1300 (NZDT) Message-ID: <4993501C.5020205@amxl.com> Date: Thu, 12 Feb 2009 11:24:28 +1300 User-Agent: Thunderbird 2.0.0.19 (X11/20090105) MIME-Version: 1.0 To: internals@lists.php.net Content-Type: multipart/mixed; boundary="------------010103070709050204090201" Subject: [Patch] Security checks to allow secure suexec CGI / FastCGI support From: php-453@amxl.com (Andrew) --------------010103070709050204090201 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit 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 --------------010103070709050204090201 Content-Type: text/x-patch; name="php-suexec-security.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="php-suexec-security.patch" 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 #include "win32/php_registry.h" #endif +#ifdef HAVE_PWD_H +#include +#endif #ifdef __riscos__ #include @@ -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 --------------010103070709050204090201--