MOPB-32-2007:PHP 4.4.5/4.4.6 session_decode() Double Free Vulnerability
Summary
When security fixes for MOPB-31-2007 were developed we demonstrated to the PHP developers that their first two attempts were not a fix for the vulnerability and only killed our described exploit path, while other exploit paths were still useable.
During the search for the correct fix a wrong backport to PHP 4 was performed that introduced a double free vulnerability into the standard php deserializer. This can lead to arbitrary code execution through modified session data.
Affected versions
Affected are PHP 4.4.5 and 4.4.6
Detailed information
One of the protections that were added to the standard php session unserializer is the following code that tries to ensure that no variable is overwritten that points to the GLOBALS array or to the session data stored in the array _SESSION.
namelen = q - p;
name = estrndup(p, namelen);
q++;
if (zend_hash_find(&EG(symbol_table), name, namelen + 1, (void **) &tmp) == SUCCESS) {
if ((Z_TYPE_PP(tmp) == IS_ARRAY && Z_ARRVAL_PP(tmp) == &EG(symbol_table))
|| *tmp == PS(http_session_vars)) {
efree(name);
goto skip;
}
}
if (has_value) {
ALLOC_INIT_ZVAL(current);
if (php_var_unserialize(¤t, (const unsigned char **)&q, endptr, &var_ha...)) {
php_set_session_var(name, namelen, current, &var_hash TSRMLS_CC);
}
zval_ptr_dtor(¤t);
}
PS_ADD_VARL(name, namelen);
skip:
efree(name);
Unfortunately when the catched error condition is triggered the code first frees the name variable and then jumps to the skip label that will again free the name variable. In PHP 5 the code is similar but only frees the variable one time.
Due to the caching of small blocks in the Zend Memory Manager this will result in two cache positions containing the same address. This means the next two memory allocations that request blocks of the correct size will point to the same area. This can be used to for example allocate a string and an array into the same area and then modify the Zend Hashtable directly through modification of the string characters. This is demonstrated in the POC.
Proof of concept, exploit or instructions to reproduce
The attached proof of concept code is just a little POC that uses the vulnerability to get a PHP string variable and a PHP array into the same place of memory. By manipulation of the PHP string the exploit overwrites the array destructor pointer which leads to an attempted execution at 0x88776655. In a real exploit this offset just needs to pointed at shellcode.
$ gdb ./php-4.4.5-basic
(gdb) run MOPB-32-2007.php
Starting program: ./php-4.4.5-basic MOPB-32-2007.php
Program received signal SIGSEGV, Segmentation fault.
0x88776655 in ?? ()
(gdb) bt
#0 0x88776655 in ?? ()
#1 0x0813f546 in zend_hash_destroy (ht=0x81d21dc) at Zend/zend_hash.c:558
#2 0x0813a391 in _zval_dtor (zvalue=0x81d22fc) at Zend/zend_variables.c:51
#3 0x081321aa in _zval_ptr_dtor (zval_ptr=0x81d22c0) at Zend/zend_execute_API.c:289
#4 0x08140894 in zend_hash_del_key_or_index (ht=0x81a0b2c, arKey=0x81d73c4 '_', "a",
nKeyLength=19, h=2758057915, flag=0) at Zend/zend_hash.c:529
#5 0x0814d753 in execute (op_array=0x81d1f8c) at Zend/zend_execute.c:2323
#6 0x0813b9ec in zend_execute_scripts (type=8, retval=0x0, file_count=3) at Zend/zend.c:935
#7 0x08110040 in php_execute_script (primary_file=0xbfe01784) at main/main.c:1757
#8 0x08157fe8 in main (argc=2, argv=0xbfe01824) at sapi/cli/php_cli.c:838
(gdb) i r $eip
eip 0x88776655 0x88776655
(gdb)
Notes
Here is a nice example how a wrongly backported security fix introduced an even more dangerous security hole.
Like with all exploits that can be triggered through malformed session data it is vital to understand, that these might be triggerable remotely through other kinds of PHP application or extension vulnerabilities. For example vulnerabilities like HPHP-05-2006 existed in the Zend Platform.