zoukankan      html  css  js  c++  java
  • Typecho V1.1反序列化导致代码执行分析

    0x00  前言

        今天在Seebug的公众号看到了Typecho的一个前台getshell分析的文章,然后自己也想来学习一下。保持对行内的关注,了解最新的漏洞很重要。

    0x01  什么是反序列化漏洞

        如题所说,这是一个反序列化导致的代码执行。看过我之前文章的人应该不会陌生。PHP在反序列化一个字符串时,相当于激活了之前休眠的一个对象。当在激活的时候,会调用几个魔法方法来进行初始化以及相关操作。而如果魔法方法中存在危险操作,比如数据库操作,文件读写操作,系统命令调用等,那么就有可能会造成相应的漏洞。用户通过可控变量调整系统运行流程。进入到危险函数,从而导致漏洞。

    0x02  分析

        官方在前两天已经做了修复。现在已经不存在这个漏洞了。我们找到以前的版本进行安装。链接如下:

        

    https://github.com/typecho/typecho/releases/tag/v1.1-15.5.12-beta
    

      

    在根目录的install.php中我们发现:

    我们定位到了漏洞现场。然后问题来了,反序列化是将一个休眠对象(字符串)激活为一个对象。那么我们接下来找到在激活的过程中,有哪些方法可以调用,它们才是利用的关键。我们来搜索这三个方法:

    __toString()
    
    __destruct()
    
    __Wakeup()
    

      我没有找到wakeup方法,然后destruct方法也不能使用。然后搜索__toString()的时候发现有三处,分别如下:

    varTypechoConfig.php
    
    public function __toString()
    {
        return serialize($this->_currentConfig);
    }
    
    
    varTypechoDbQuery.php
    
    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;
            }
        }
    
    varTypechoFeed.php
    
        public function __toString()
        {
            。。。后边再贴代码
        }
    

    但是一定能用吗?

    我们可以看到随后的$config进入了Typecho_Db类的构造方法,我们来跟进:

    在第120行的时候,发现传入的参数用来做字符串拼接。这样就会调用__toString()方法了.

      

    然后我们对之前搜索到的方法跟进,发现前两个是没法用的。然后我们重点关注最后一个。这个函数主要的目的是拼接xml文件。然后在整个过程中,并没有直接进入危险函数,所以此时陷入僵局。但是就这样就无解了吗?

    0x03  __get()方法的逆袭

    __get()方法在调用一个对象不存在的成员变量时候将会调用此方法。可以参考我之前的文章。

    那么这样的话我们只要能够找到一个get方法中存在危险操作,这样的话,这个漏洞还是可能的。我们来试试看。

    全局来搜索__get()方法,然后在varTypechoRequest.php

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

    在方法的最后调用了_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;
        }

    我们来梳理一下整个逻辑,install.php (反序列化) ==> Db.php (将$config中的参数拿来拼接,调用toString方法) ==> Feed.php (调用一个不存在成员变量,将会调用__get()方法) ==> Request.php (调用get()方法,然后调用_applyFilter(), 然后调用call_user_func()导致了代码执行)

    0x03  利用方法

        完整的利用过程,我们可以看

    p0的文章 http://p0sec.net/index.php/archives/114/ 或者
    
    Seebug的公众号 http://mp.weixin.qq.com/s?__biz=MzAxNDY2MTQ2OQ==&mid=2650942666&idx=1&sn=5c84d6d69463a0a430e01dfa68c2d3ab&chksm=80796ef8b70ee7ee8ba5d88feb8d794bee19a55b6e17dff45fcee6ba6ee726fb4e2e029d50bd&scene=0#rd

      

    0x04  思考

        这个其实是当时看到122行的时候:

    我想这里直接调用了call_user_func()函数,$adapterName我是可控的,我们不直接可以命令执行吗?还要长长的执行链干什么啊?

    然后我查了call_user_func()的使用方法:

    如果传入的是数组,那么数组中的第一个值是一个类名,第二个是类中的一个方法。但是怎么样第一个值才能是类呢?这个时候我想到了匿名类。在函数式编程中经常会用到匿名函数,匿名类。于是我去查了一下发现在PHP7中加入了匿名类的定义,所以如下代码是可以的:

    call_user_func(array(
            new class(){
                public function isAvaliable() {
                    echo "Hello, World";
                }
            }, 
            'isAvaliable'
        )
    );
    
    //执行结果
    Hello, World

     这里不能用的原因在于被之前的$adapterName = 'Typecho_Db_Adapter_' . $adapterName;给打断掉了。如果你用类和字符串做拼接。很明显是不行的。

      

  • 相关阅读:
    Spring boot 整合mybatis
    验证码图片生成器
    Guava
    高并发环境下生成唯一流水号
    BP神经网络
    c# 利用反射获取属性名和值
    第一天开博,试试发个贴
    更改UISearchBar系统背景色方法
    IOS开发之UISearchBar应用
    textview根据文字行数自动变化大小
  • 原文地址:https://www.cnblogs.com/magic-zero/p/7737274.html
Copyright © 2011-2022 走看看