# PHP中的内存破坏漏洞利用(CVE-2014-8142和CVE-2015-0231)(连载之第二篇)
0x00 前言
=======
* * *
作者:Cigital公司的安全顾问Qsl1pknotp(Tim Michaud)
题目:Exploiting memory corruption bugs in PHP (CVE-2014-8142 and CVE-2015-0231) Part 2:Remote Exploitation
地址:http://www.inulledmyself.com/2015/02/exploiting-memory-corruption-bugs-in_23.html
上一部分中,我们找到了本地利用CVE-2014-8142和CVE-2015-0231的方法。在第二部分中,我们将进一步探讨漏洞的远程利用,并明确通过我们的方法到底能窃取到什么有用信息。本部分的研究是只针对CVE-2015-0231的,至于CVE-2014-8142的远程利用,其实读者完全可以根据第一部分中的概述,自己做一些修改来完成。
0x01 PHP中 “序列化”特性应用
===================
* * *
上一部分中我们讲到,Esser给出的代码可以泄露一个攻击者不可控的地址的数据。代码如下:
“`
“`
source:[StefanEsser_Original_LocalMemLeak.php](https://gist.github.com/tmm08a/686d3f78a44c8ea80fd0#file-stefanesser_original_localmemleak-php)
虽然上述代码是有用的,但是它没有达到我们期待的效果!我们想要的是远程泄露任意内存地址的数据,而不是基本上没什么作用的随机地址数据。为了做到这一点,我们需要找到一种方法来完成以下两个目标:
写任意数据(确保PHP不崩溃)
读任意数据(确保PHP不崩溃)
跟生活中的其他事情一样,一次只解决一个问题往往要容易一些。因此,我们首先从完成目标1开始做起。其实我们是可以做到写任何我们所需的数据的,因为发送的是自己的object对象。然而,我们需要的是找到方法来写有用的信息,如下是我们上一回中讲到的最后一个例子:
“`
ccc;
?>
“`
source:[PHPLeak](https://gist.github.com/tmm08a/4c3130001a258e45d39f#file-phpleak)
这里我们把关注重点放在$fakezval变量上。有没有办法可以在序列化对象中远程写该zval?(提示:这是php的一个“特性” :D!) (顺便提醒下,千万不要像我一样的愚蠢而懒惰,一定要仔细地去读所有的代码。在找到这部分如此明显的代码之前,我就浪费了5、6个小时的时间。)
幸好,PHP中字符串有一个“序列化”特性。序列化字符串中的该特性允许我们序列化和反序列化二进制数据。让我们动手操作一个S对象,对序列化原理加深理解,并进一步学会如何利用该特性!
“`
“`
source:[SendBinaryData](https://gist.github.com/tmm08a/9d7cb3bf3ba2a94789c2#file-sendbinarydata)
接下来运行上述代码,我们可以看到一个奇怪结果:
![enter image description here](http://drops.javaweb.org/uploads/images/a7a8527c62d1742a90d7b10d3b9e88519bca7402.jpg)
其实这也没什么奇怪的,我们明显是程序中写错了些什么。为免大家捂脸悲叹,本人直接提示了,这个错误肯定跟我们的S对象有关,你可以一个一个地数下$data的字节数!是的,我也知道PHP的错误提示已经烂到了什么都不提示的地步了……
在正常化的序列化字符串中,例如s:3:”123”,整数3代表字符串包含的字符数。而在上面的代码中,我们用的S:43:”\00\01\00\00AAAA\00\01\01\00\01\00\0B\BC\CC”中也有一个整数(43),应该同样表示字符串的长度,对吗?
其实并非如此。我们这里想要的不是一个文字字符串,而是可以使PHP解释器按二进制数据来解析的二进制字符串,而这里的字符串也并非43个字节,而是17个字节。那么将43改为17试一下。
![enter image description here](http://drops.javaweb.org/uploads/images/b2085e57b2aab2421da715708bf1936080796961.jpg)
这下好了!那么为什么要用17呢?每一个\xx会被当做一个字符,这样我们的字符串中就有13个字符了,而“AAAA”会被当做正常的字符来解析,因此长度需要再加4。简言之,每一个\xx“三元组”被当做是一个字符。Ok,现在我们可以发送该字符串,但是我们该如何从解释器中获取想要信息呢?
在可以泄露任意地址之前,让我们一起再多学习下服务器本身,这会有助于我们写出更加可靠的利用程序(第三部分中详述)。作为练手,我们学习下如何确定服务器的字节顺序,这将需要使用一个伪装的整型zval结构。 想法还是一样的:
* 创建一个整型数组
* 释放掉刚创建的整型数组
* 创建我们之前例子中的字符串
* 指向我们之前释放掉的整型数组的引用地址
为什么要使用整型zval而不是一个string?我们回想一下zval数据结构,整型看起来将如下所示:
* 我们来设置下整型的值
00 01 00 00 小尾方式下表示0x100 大尾方式下表示0x1000
* 将接下来的8个字节使用0x41填充
41 41 41 41(或AAAA)
* 接下来的8个字节是引用计数
00 01 01 00
* 最后的8个字节是01(代表整型类型),然后我们将剩下的字节填充为垃圾数
01 00 bc cc
把上述的放在一起,就得到了“S”的值!那么如何通过上述的结构确定服务器的字节顺序呢?在服务器的响应中,如果返回0x100(256),就可以确定是小尾方式!如果返回的是65536,那么就是大尾方式!确定字节顺序的完整php代码如下:
“`
“`
source:[determineEndianness](https://gist.github.com/tmm08a/66810644f89f293a7b8b#file-determineendianness)
程序响应如下:
![enter image description here](http://drops.javaweb.org/uploads/images/aedbfe5b7cf24bfe4f9e0a04e9532ce8c8e17141.jpg)
很好!现在我们已经能够确定服务器的字节顺序了!当然,我们真正想要的仍然是泄露任意数据。让我们接着往下走,既然已经能够泄露出我们所提供的数据了,那么是不是有可能泄露出任意地址的数据呢?
0x02 任意地址数据泄露
=============
* * *
本篇文章并未就此结束,因而我们的答案当然是肯定的!不过,这次我们需要的不再是整型的zval结构,而是字符串型zval数据结构,结构如下:
* 设置指向字符串数据的指针
00 80 04 08
* 设置我们想要获得的字符串长度(1024)
00 04 00 00
* 设置引用计数为非零
00 01 01 00
* 最后,设置数据类型为string型(06),其余字节设为垃圾数
06 00 0b bc
我们的新脚本如下所示:
“`
“`
source:[leakDataAtAddress](https://gist.github.com/tmm08a/5013927665891b0f0de2#file-leakdataataddress)
运行后的结果如下:
![enter image description here](http://drops.javaweb.org/uploads/images/065bd5ea75bb04c10642d56008c2f00aa6836f22.jpg)
好极了!我们现在已经可以dump出任意地址的数据了,当然也要知道地址才行,而这是不实际的。那么我们如何远程来提取地址呢?当然我们可以使用上一回中给出的代码来泄露地址,但是泄露出来的地址并未指向重要数据。有没有别的机制可以用来提取地址信息呢?
0x03 地址信息远程提取
=============
* * *
值得庆幸的是,确实有办法来提取地址信息!看下面的代码:
“`
“`
source:[leakLegitimateAddress](https://gist.github.com/tmm08a/5d99e869417f1c52b22d#file-leaklegitimateaddress)
运行后如下所示:
![enter image description here](http://drops.javaweb.org/uploads/images/650a8e0b1b9de9dba418c9cea921cacad5be870e.jpg)
这是个相当大的数组,需要分解来看。总体思路如下:
* 创建整型数组#1 将会清空内存缓存
* 创建整型数组#2 填入变量表
* 释放掉数组#2 释放掉变量表中的每个节点
* 创建一个混合了S对象的对象数组
* 释放掉对象数组 见下面的解释
* 指向已经释放掉并且又被覆盖了的数组#2中的一个整型值
* 服务器的响应中包将含有价值的数据
释放掉对象数组后,数组中的前四个字节就会被内存缓存重写(因此该内存重新变为可写)。在这样做时,字符串指针(之前是0x41414141)现在就指向了之前被释放的内存对象。得到的地址太多,这里不一一列举了,但不管如何,我们得到了合法的内存地址! 但是我们需要的是哪个地址呢?我们要找的是显示有 “\x00\x00\x00\x00\x05\x00”的地址。这样的地址是一个对象句柄(数据段中的一个数据结构)地址。现在,我们可以读取整个对象句柄表,并获取PHP代码段中的信息(而这也正是我们感兴趣的地方,因为我们下一回想要做的就是弹出一个shell)
下面是查看PHP返回的16进制数据的命令:
“`
cphp newLeak.php | xxd -ps | sed ‘s/[[:xdigit:]]\{2\}/\\x&/g’
“`
执行命令,我们用grep查找“\x00\x00\x00\x00\x05\x00”,得到如下地址:
![enter image description here](http://drops.javaweb.org/uploads/images/5cca21840902300d98cae6d8be454c8370ab17df.jpg)
如果在GDB中加载运行,我们也可以看到这个地址实际上是指向我们对象句柄的一个指针。提醒:运行前别忘记下断点(我是在var_unserializer.c:337中设了一个)。
![enter image description here](http://drops.javaweb.org/uploads/images/1d428459426bfce8a42ca717cc41de42b6ebee0c.jpg)
我们看到的如下所示:
![enter image description here](http://drops.javaweb.org/uploads/images/5d897113c53ad30d477c9aac60a07185ef8b1e20.jpg)
在庆祝之前,让我们先确认下这些句柄指向的确实是有用的东西,就看第一个入口点吧:0x0830a640。下面是该地址存储的数据:
![enter image description here](http://drops.javaweb.org/uploads/images/1ef3316e2406f37b40c21e827a73cf81efe17e29.jpg)
好极了!我们现在已经可以看到任何我们想要的数据了!
0x04 可窃取信息
==========
* * *
总结一下,通过结合上述的这些方法,我们就可以获取到: 完整的PHP二进制可执行文件本身(包括其数据) SSL Certs(通过mod_ssl) PHP符号表 别的模块的地址(及其数据)
0x05 下一步研究
==========
* * *
第三部分中,我们将探讨如何弹出一个Shell!该技术也可用于CVE-2015-0273,以及其他的PHP UAF漏洞利用中。
PS:第三部分的释出需要一点时间,因为要完成进一步的利用还需要对PHP做深入的研究(包括阅读一些资料),但是本人保证一定完成,绝不太监,敬请继续关注。
请登录后查看评论内容