上篇《php反序列化从入门到放弃(入门篇)》主要总结了PHP的反序列化的一些知识,本篇主要通过cms实例来更好的理解并且挖掘反序列化漏洞。
以下cms的源码地址:https://github.com/bmjoker/Code-audit/
Typecho1.0.14反序列化导致任意代码执行
Typecho是一个PHP版本的轻量版博客系统,存在反序列化导致前台getshell的漏洞,通过分析这个漏洞来深入理解PHP反序列化漏洞。
漏洞的触发点是install.php中的反序列化方法unserialize():
先来看一下访问到unserialize()反序列化方法的前置条件
$SERVER['HTTP_REFERER']需要与$_SERVER['HTTP_HOST']的值相等,意思是请求包中的字段 host==referer['host']。
这几个is..else的意思是如果想要执行的漏洞代码,需要满足:
1. 通过GET请求接收到的finish参数不为空;
2. __TYPECHO_ROOT_DIR__/config.inc.php文件不存在;
3. cookie中或者POST方法传进来的参数中存在__typecho_config字段的值。
具体分析一下漏洞代码
跟进查看Typecho_Cookie这个类的get方法
将Cookie中的__typecho_config或者POST传过来的__typecho_config的值取出来,使用base64解密,然后通过unserialize进行反序列化,并将反序列化之后的结果赋予变量$config。
漏洞的触发点就在于这个unserialize()方法,如果存在可以利用的漏洞利用点,像file_put_contents,exec...,就可以在__typecho_config中
构造特定的序列化payload数据来实现漏洞的利用,比如任意代码执行等。
继续往下看,发现实例化一个Typecho_Db类的对象,并把$config['adapter']和$config['prefix']传入Typecho_Db类中进行实例化,然后调用Typecho_Db的addServer方法对$config进行处理,跟进一下Typecho_Db类:
这里看到:
$this->_adapterName = $adapterName;
$adapterName = 'Typecho_Db_Adapter_' . $adapterName;
在入门篇都有提过魔术方法__toString()的几种触发方式。像上面的代码,如果$adapterName是一个实例化对象,在进行了字符串的拼接的时候就会触发该类的__toString()魔术方法。
全局搜索一下魔术方法__toString():
这里发现Config.php,Feed.php,Query.php三个文件都包含__toString()方法。现在的目的是依次去分析三个php文件中的__toString()方法,寻找漏洞的利用点,构造相应的反序列化链。
Config.php --> __toString()
这里调用__toString()做了序列化???没什么利用点,pass。
Query.php --> __toString()
在492行看到如下代码
如果$this->_adapter是一个不存在parseSelect方法的类的对象的时候,那么调用parseSelect方法就是访问一个不可访问的方法,就会触发该类的魔术方法__call()。全局搜一下魔术方法__call(),最后发现Plugin.php有以下代码:
$component是调用失败的方法名,$args是调用时的参数。均可控,但是根据上文,$args必须存在array('action'=>'SELECT'),然后加上我们构造的payload,最少是个长度为2的数组,但是483行又给数组加了一个长度,导致$args长度至少为3,那么call_user_func_array()便无法正常执行。所以此路就不通了
Feed.php --> __toString()
在290行看到如下代码
这里的$item由foreach()循环得来,使用$item['author'->screenName]获取author对应的screenName属性,这里就存在一个序列化链的构造点,如果$item['author']是一个不存在screenName属性的类的对象的时候,那么访问screenName就是访问一个不存在的属性,就会触发该类的魔术方法__get()。全局搜一下魔术方法__get(),查找利用点:
这里不再一个一个分析了,最后在Request.php中找到了利用链:
跟进get方法
$value是$this->_params[$key]的值,$key就是screenName,是可以控制的输入值,继续跟进_applyFilter方法:
参数$filter和$value都可控,并且call_user_func()和array_map()方法都可通过回调函数实现任意代码执行。
最终的调用链如下:
call_user_func <-- Typecho_Request::_applyFilter <-- Typecho_Request::get <-- Typecho_Request::__get <-- Typecho_Feed::__toString <-- Typecho_Db::__construct
构造序列化payload:
<?php
class Typecho_Feed{
private $_items=array();
private $_type='RSS 2.0';
public function __construct(){
$this->_items[0]=array(
'category' => array(new Typecho_Request()),
'author' => new Typecho_Request()
);
}
}
class Typecho_Request{
private $_filter = array();
private $_params = array();
function __construct(){
$this->_params['screenName'] = 'phpinfo();';
$this->_filter[0] = 'assert';
}
}
$exp = array(
'adapter' => new Typecho_Feed(),
'prefix' => 'typecho_',
);
print_r(base64_encode(serialize($exp)));
?>
成功复现。
来看一下官方的补丁:
更换判断是否安装的方法,删除反序列化代码。
Joomla3.4.6反序列化导致任意代码执行
Joomla反序列化漏洞的主要原因是:
Joomla不论账号密码是否正确,都会把登录的用户名和密码,通过序列化的方式存储在session表中,再以反序列化的方式读取session表中的内容。由于protected修饰的变量在序列化的时候,会变成x00 + * + x00 + [变量名]的形式,而mysql无法保存NULL字节的数据,所以在向session表写入的过程中会将 x00*x00替换为