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()的時候發現有三處,分別如下:

var\Typecho\Config.php

public function __toString()
{
    return serialize($this->_currentConfig);
}


var\Typecho\Db\Query.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;
        }
    }

\var\Typecho\Feed.php

    public function __toString()
    {
        。。。后邊再貼代碼
    }

但是一定能用嗎?

我們可以看到隨后的$config進入了Typecho_Db類的構造方法,我們來跟進:

在第120行的時候,發現傳入的參數用來做字符串拼接。這樣就會調用__toString()方法了.

  

然后我們對之前搜索到的方法跟進,發現前兩個是沒法用的。然后我們重點關注最后一個。這個函數主要的目的是拼接xml文件。然后在整個過程中,並沒有直接進入危險函數,所以此時陷入僵局。但是就這樣就無解了嗎?

 

0x03  __get()方法的逆襲

__get()方法在調用一個對象不存在的成員變量時候將會調用此方法。可以參考我之前的文章。

那么這樣的話我們只要能夠找到一個get方法中存在危險操作,這樣的話,這個漏洞還是可能的。我們來試試看。

全局來搜索__get()方法,然后在\var\Typecho\Request.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;給打斷掉了。如果你用類和字符串做拼接。很明顯是不行的。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM