香港独立IP空间
香港php空间
美国php空间
域名注册

Typecho是一个轻量级的开源博客系统,近日有网友爆出该系统的安装文件install.php存在反序列化可控点,能导致任意代码执行。
快速修补方法:
删除install.php及install目录
漏洞细节:
在 install.php 文件的 288-235行

    $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_Db的addServer方法,调用Typecho_Config实例化工厂函数对Typecho_Config类进行实例化。

跟踪 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()魔术方法。
继续跟进发现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 .= '_baseUrl . '"
>' . self::EOL;

            $content = '';
            $lastUpdate = 0;

            foreach ($this->_items as $item) {
                $content .= '' . self::EOL;
                $content .= '<![CDATA[' . $item['title'] . ']]>' . self::EOL;
                $content .= '' . self::EOL;
                $content .= '' . $item['link'] . '' . self::EOL;
                $content .= '' . $this-&gt;dateFormat($item['date']) . '' . self::EOL;
                $content .= '' . $this-&gt;dateFormat($item['date']) . '' . self::EOL;
                $content .= '
    ' . $item['author']-&gt;screenName . '
    ' . $item['author']-&gt;url . '
' . 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均可造成任意代码执行。

本文地址:http://www.51php.com/safety/25764.html

喜欢本文或觉得本文对您有帮助,请分享给您的朋友 ^_^

阅读本文的人还阅读: