$GLOBALS Overwrite and it's Consequences





Last update: 2. November 2005

In PHP exists a special group of variable arrays called the superglobals. These variables have im common, that they are known to all variable scopes and therefore can be accessed from within any function or method without hassle. They are used within PHP to store the among other things the GET, POST, COOKIE and SESSION variables. However one of the superglobals, that is called $GLOBALS is special in two ways. Firstly it is merely a reference to the global symbol table, because it's purpose is to access variables from the global symbol table from within functions or methods in an easy way. Secondly it is implemented in a different way, than the rest of the superglobals and therefore unexpected things may happen when the $GLOBALS variable is overwritten.

$GLOBALS in PHP4

To properly understand the problems that can arise when $GLOBALS is overwritten it is necessary to have a look on how it is implemented within the different major versions of PHP. In PHP4 $GLOBALS is created by the executor whenever the execution of the main program, a function, a method or evaluated code is started. This is done by inserting the key 'GLOBALS' into the current local symbol table as a reference to the global symbol table. This can however fail if the local symbol table already contains an element called 'GLOBALS'. The problem with this behaviour is, that when GLOBALS is overwritten in one variable scope (f.e. in the scope of the main program) it gets a different meaning and content, than GLOBALS in the scope of subfunctions, because for those the GLOBALS variable is recreated. This means after it is overwritten within a variable scope it looses the binding to the global symbol table and therefore it is no longer pointing to the global variables.

To understand the security implications of this the following example from within PEAR.php is usefull:
<?php
 $GLOBALS['_PEAR_shutdown_funcs'] = array();
 
 function _PEAR_call_destructors()
 { 
   ...
   // Now call the shutdown functions
   if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_sh... {
     foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
       call_user_func_array($value[0], $value[1]);
     }
  }
 
 register_shutdown_function("_PEAR_call_destructors");
?>

This code allows to register a number of destructors with PEAR, that get called when the request shutdown is triggered. Under normal circumstances this code would be considered safe, because it first initialises the global variable _PEAR_shutdown_funcs with an empty array, before it is used. Unfortunately this code is not safe if it is included after the $GLOBALS array was overwritten in the main program, because this would kill the connection between $GLOBALS and the global symbol table for the rest of the main scope. This means the function _PEAR_call_destructors() would actually use an unitialised global variable to retrieve the destructors and any number of parameters for it. It is obvious that this could result in remote execution of any PHP code, if an attacker is able to destroy the binding of $GLOBALS and inject global variables at the same time. Unfortunately the register_globals mode of PHP was not protected at all against overwritting GLOBALS from the outside until PHP 4.3.11 and this protection had a hole before PHP 4.4.1, which means when register_globals is turned on in PHP versions before 4.4.1 it is possible to exploit code sequences like the one found above. This means that any PHP application that includes PEAR.php from PHP <= 4.3.10 and is running in PHP < 4.4.1 with register_globals turned on, can be exploited to execute arbitrary PHP code through the piece of code above. If the PEAR.php is from a later PHP version it is still exploitable with the difference, that the attacked application must actually use an object derived from the PEAR class.

It is necessary to understand, that this is not a vulnerability in PEAR, but in the way PHP handles the GLOBALS array and that not only PEAR but a LOT of other applications are affected. PEAR::XML_RPC would also be easily exploitable through this hole, if we had not removed the use of eval() completely in one of the last versions. It is also necessary to understand, that disabling register_globals is not enough to fix this kind of vulnerability once and for all, because not only register_globals but also a lot of "register_globals compatibility layers" in a lot of applications are affected by this problem.

These applications usually emulate the function of register_globals by calling extract(), import_request_variables() or their own subroutine to merge the request variables into the global namespace. Therefore PHP4 >= 4.4.1 adds checks to extract() and import_request_variables() to protect them against overwriting the $GLOBALS variable. This however does not protect against applications that use their own routines to globalize (see the example).

<?php
   foreach ($_REQUEST as $key => $value) $$key = $value;
?>

However this type of manual globalizing is protected if you run my Hardening-Patch for PHP (http://www.hardened-php.net) because it disallows that keys named like any of the superglobals can be submitted as request variables.

$GLOBALS in PHP5

In PHP5 the $GLOBALS array is implemented as a real superglobal, that is registered before anything else is added to the main symbol table. This means, when it is overwritten in PHP5 it has a different impact on the application, than in PHP4. Because it is registered before the request variables are parsed and because there was no (or better no working) protection in PHP <= 5.0.5 it was possible to overwrite $GLOBALS from the outside when register_globals is turned on, or extract() or import_request_variables() were used in an unsafe way. The major difference between the impact on PHP4 and PHP5 is, that due to the real superglobals nature of $GLOBALS the overwrite will not only be visible in the scope where it happened, but in all scopes. However the danger for application running within PHP5 is, that the binding between the $GLOBALS and the global symbol table is broken. The following code illustrates the problem:
<?php
  $include_path = dirname(__FILE__)."includes/";

  include $include_path."common.php";
  
  load_language('de');

  function load_language($lang)
  { 
    // no need to check that $lang 
    // is a 'nice' language because 
    // only internally used

    include $GLOBALS['include_path']."/lang/$lang.php";
  }
?>
While the code above is not nice, it is still a common piece of code in PHP applications and under normal circumstances it is safe. However because of the $GLOBALS overwrite problem this code is NOT SAFE it actually contains a remote URL include vulnerability, because it is possible in PHP5 <= 5.0.5 to overwrite the $GLOBALS variable to unbind it from the global symbol table and then inject an own value for $GLOBALS['include_path'] that will result in the function load_language() including an arbitrary (remote) file and therefore execute arbitrary PHP code.

A similiar piece of code can be found in a lot of applications. Additionally this vulnerability of PHP5 can be used to bypass the register_globals deregistration layer of f.e. vBulletin, which results in atleast one critical SQL injection vulnerability in it, which will not be disclosed at this time. (The bug within vBulletin is fixed by implementing a workaround as of 2. November 2005). This vulnerability can also be used to cause a remote URL include in previous versions of phpMyAdmin. With the last bugfix release a protection against this kind of problem was added.

Impact of GLOBALS overwrite

The impact of this kind of vulnerabilities is very high, because the problematic code seems to be secure unless you know about this behaviour of PHP and therefore very many applications are vulnerable to this problem. Additionally it is problematic, that the heart of PEAR (PEAR.php) also suffers from this vulnerability in PHP, although their code is written in a way that should be safe onder normal circumstances.

According to PHP usage statistics 71.03% of all servers, that announce a PHP4 version within their HTTP headers, are still using PHP <= 4.3.10. This actually means that most of the servers out there are running with old versions of PEAR where a application gets automatically vulnerable if it includes PEAR.php and is running with register_globals turned on. And this also means, that any PHP application that suffers from a local file include vulnerability can be easily turned into a remote code execution vulnerability by simply including the local copy of PEAR.php, that is usually in standard (or easyly guessable) paths. Additionally the PEAR directory is often trusted and therefore added as safe_mode_include_dir or to the open_basedir, so that it is even possible to include PEAR.php if SAFE_MODE or open_basedir is used to secure the system.

In the end it is simply unknown how many PHP applications suffer from these problems, because the problem is often overseen, widespread and unknown to a lot of security auditors. And with PEAR.php and vBulletin there are already two very big names on the list of affected applications. (vBulletin has released new versions with a workaround for this vulnerability for customers that cannot easily update their PHP version)

Stefan Esser
© Hardened PHP Project