Thu, 01 Mar 2007


The Month of PHP Bugs starts with a PHP 4 security vulnerability that exploits a problem known for many years among the PHP developers.

When a PHP application is run in PHP 4 it can overflow the variable reference counter because it is only 16 bit wide. Whenever this happens it will result in a double destruction of the underlying variable. A local attacker can easily create PHP code that uses such a double destruction to execute arbitrary code within the process executing PHP (e.g. webserver process). This allows bypassing restrictions enforced by disable_functions, open_basedir, SAFE_MODE or to launch direct local root exploits against the target system.

Affected versions

All versions of PHP 4

Detailed information

In PHP 4 the ZVAL structure that internally describes a variable looks like this

struct _zval_struct {
        /* Variable information */
        zvalue_value value;     /* value */
        zend_uchar type;        /* active type */
        zend_uchar is_ref;
        zend_ushort refcount;

When this structure was designed the reference counter was made 16 bit wide so that the whole structure is exactly 8 bytes long (on 32bit systems). In PHP 5 this field is meanwhile 32 bit wide, because 16 bit can overflow too easily and PHP does not have an internal protection against overflows of the reference counter. Unfortunately for PHP 4 this means code like this will overflow the counter and will trigger a double destruction of the variable on script end.

  $var = "POC";
  for ($i = 0; $i < 0x10001; $i++) {
    $arr[] = &$var;

An attacker can exploit this by overflowing the reference counter. Then he deletes one of the references which will result in the variable being destructed, because PHP knows only the lower 16 bit of the real refcount which is 1 in that case. When the variable is freed he carefully crafts some other variables that then get allocated into the same memory. He then frees all the other references which will trigger another destruction at refcount 1.

Our proof of concept code shows how an attacker can put the internal header of a PHP array into the same place of a string. This enables him to read and write values from the internal header by reading string offsets. A code execution exploit it as simple as writing your own pointer over the destructor field of the internal array header and delete the array variable afterwards.

Proof of concept, exploit or instructions to reproduce

The attached exploit will not run directly. You have to first remove the die() command that protects it from being remotely included. Additionally the exploit comes without real shellcode. It will only trigger the debugger when executed. However you can put in any shellcode you like, just make sure it is long enough (e.g. 500 bytes).

The exploit is designed for 32 bit systems and should work out of the box on little endian and big endian systems. It does not need any kind of offset, because it leaks the shellcode address to itself.

$ gdb ./php-4.4.4

(gdb) run exploit.php
Starting program: /home/code/MOPB/php-4.4.4 exploit.php

Program received signal SIGTRAP, Trace/breakpoint trap.
0x08573e95 in ?? ()
(gdb) x/5i $eip
0x8573e95:      int3
0x8573e96:      int3
0x8573e97:      int3
0x8573e98:      int3
0x8573e99:      int3


Because the PHP developers do not want to fix this anymore because it creates problems for companies providing closed source PHP extensions the only potential workaround is to manually change the size of the reference counter in your own PHP. However if you do so you have to recompile all your PHP extensions and cannot use closed source PHP extensions anymore.