Fri, 16 Mar 2007


The internal function array_user_key_compare() is used for example by the PHP function uksort(). Its purpose is to call the userspace array key comparison function with the keys to compare as parameters. When the called function returns both parameters are destructed even if the userspace handler created references. Because of this pointers to already destructed ZVALs are left in the symboltable which will result in an exploitable double DTOR situation that allows the execution of arbitrary code.

Affected versions

Affected are PHP 4 <= 4.4.6 and PHP 5 <= 5.2.1

Detailed information

When for example uksort() calls the internal array_user_key_compare() to execute the userspace key comparison it will extract the keys from the two Hashbuckets a and b into the ZVALs key1 and key2. These ZVALs are then passed to the userspace function and afterwards destroyed by calls to zval_dtor().

static int array_user_key_compare(const void *a, const void *b TSRMLS_DC)
    zval key1, key2;
    zval *args[2];
    args[0] = &key1;
    args[1] = &key2;
    status = call_user_function(EG(function_table), NULL, *BG(user_compare_func_name), &retval, 2, args TSRMLS_CC);


The problem with this code is that the userspace function might have created references to the supplied keys. By simply destroying the variables behind those keys with zval_dtor() the references will point to already freed memory, which can result in all kinds of memory corruptions that can be exploited to execute arbitrary code, as demonstrated by the attached exploit.

The correct way to fix this issue would be replacing the calls to zval_dtor() with calls to zval_ptr_dtor() that takes the reference counter into account before destroying the variables.

Proof of concept, exploit or instructions to reproduce

The attached exploit uses the trick already known from the very first bug, to put a string into the same place as a Hashtable, which allows reading the address to the first hashentry bucket from the string and using it to determine the offset of the shellcode that is then written through the same string over the Hashtable destructor field. The code execution is then triggered when the Hashtable is destructed.


This vulnerability is only exploitable locally. However the exploit shows how easy it is to execute arbitrary machine code from within PHP 4 and PHP 5, which renders any kind of in-built protection of PHP, like disable_functions, open_basedir and safe_mode completely useless.