作者:LoRexxar'@知道創宇404實驗室
日期:2021年2月5日

反序列化漏洞是PHP漏洞中重要的一個印象面,而反序列化漏洞的危害則需要反序列化鏈來界定,如何挖掘一條反序列化鏈,往往成為了漏洞挖掘中最浪費時間的部分。

而和挖掘漏洞一樣,建立在流敏感分析基礎上的自動化白盒漏洞掃描技術,依賴數據流中大量的語法節點數據,通過合理的分析手段,我們就可以回溯分析挖掘漏洞,而挖掘php反序列化鏈也一樣,只要有合理的分析思路,那么我們就可以通過分析數據流來獲得我們想要的結果。

今天我們就來一起聊聊,如何把人工審計轉化成自動化挖掘方案吧~

如何挖掘一個PHP反序列化鏈

反序列化漏洞的原理這里就不再贅述了,而PoP鏈的核心,就是魔術方法。而php的魔術方法中涉及到反序列化的大致有以下幾種:

__destruct: 析構函數,會在到某個對象的所有引用都被刪除或者當對象被顯式銷毀時執行。一般來說,也是Pop鏈的入口。
__toString: 類對象遇到字符串操作時觸發。
__wakeup:   類實例反序列化時觸發。
__call:    當調用了類對象中不存在或者不可訪問的方法時觸發。
__callStatic: 當調用了類對象中不可訪問的靜態方法時觸發。
__get:     當獲取了類對象中不可訪問的屬性時觸發。    
__set:     當試圖向類對象中不可訪問的屬性賦值時觸發。
__invoke:   當對象調用為函數時觸發

通俗來講,我們可以把__destruct當作挖掘反序列化鏈的入口,因為__wakeup一般內容為反序列化的限制。

__destruct開始,我們探討,在不同的情況下我們分別會如何尋找調用鏈?

__call__callstatic

當代碼出現

function __destruct(){
    $this->a();
}

我們可以直接跟下去看a方法的內容,如果當前類不存在a方法,則會優先查找父類的a方法。如果父類也不存在a方法(或是不可訪問),那么就會觸發當前類的__call魔術方法。

$this->a() ==> 當前類a方法 ==> 父類a方法 ==> 當前類__call方法 ==> 父類__call方法

值得注意的是,如果觸發__call方法,那么a會作為__call的方法的第一個參數,而參數列表會作為__call的方法第二個參數。

而當代碼出現

function __destruct(){
    $this->a->b();
}

此時,我們便不用糾結于$this->a是代表什么類了,我們可以調用任意類的b方法。換言之,我們也可以調用任意沒有b方法的類對象的__call方法。

 $this->a->b() ==> 任意類的b方法 ==> 任意類的`__call`方法

__callstatic和call大同小異,唯一的區別就是當調用靜態方法時觸發,例如:

function __destruct(){
    $this::a();
}

但可惜的是__callstatic一般來說都不會有太有價值的代碼。

__get__set

當代碼出現

function __destruct(){
    echo $this->a;
}

這時echo會優先訪問當前類的a變量,然后尋找父類的a變量,如果不存在該類變量或者不可訪問時,則會調用對應的__get方法

image-20210204163257467

$this->a ==> 當前類a變量 ==> 父類a變量 ==> 當前類__get方法 ==> 父類__get方法

同樣,如果調用$this->a->b,我們就有可能觸發任意類的__get方法。

而當代碼出現

function __destruct(){
    $this->a = 1;
}

如果當前類不存在a變量時,則會觸發__set方法

image-20210204165309768

__toString

__toSring是一個很特別的魔術方法,當類對象遇到字符串操作時觸發。

他一般常見于這種代碼中

function __destruct(){
    echo $this->a;
}

當我們控制$this->a時,我們就可以觸發任意類的__toSring方法。

image-20210204170419508

echo $this->a ==> 任意類的__toSring

其他方法

其他方法主要包括__wakeup__invoke,這兩種方法比較特殊,在反序列化鏈中出現的概率比較小。

由于__wakeup是在反序列化時執行,所以一般來說,開發者會傾向于在wakeup函數中加入過濾部分,以減少反序列化漏洞的危害。

__invoke觸發條件是當嘗試以調用函數的方式調用一個對象時。但可惜的是,如果你試圖調用一個方法時,會優先執行__call邏輯,而不是invoke。

image-20210204172009929

換言之,只有經過二次賦值的代碼才有可能觸發這個函數

image-20210204172252298

在實際環境中,很難見到這樣的代碼出現了。

在了解了挖掘反序列化鏈的基礎知識后,我們就把前面的思路整理整理,一起來看看怎么寫一個自動化挖掘php反序列化鏈的小工具吧。

完成一個自動化挖掘php反序列化鏈的小工具

不知道為什么寫到這里感覺有點兒像 :>

img

到這里為止,你需要我之前的文章構造一個 CodeDB 來探索全新的白盒靜態掃描方案的一些前置知識。

現在我們手里有了一張通過格式化AST數據流生成的CodeDB表,我們的目標是完成一個能自動化挖掘php反序列化的工具。

現在我們至少擁有了這樣一個思路。首先,我們需要尋找所有的destruct函數

image-20210204182124042

首先,我們加入對call方法的分析

$this->a() ==> 當前類a方法 ==> 父類a方法 ==> 當前類__call方法 ==> 父類__call方法

那么流程圖就變成了 image-20210204183416313

緊接著我們要加入__get__set兩部分的處理。

$this->a ==> 當前類a變量 ==> 父類a變量 ==> 當前類__get方法 ==> 父類__get方法

image-20210205105145831

在這個基礎上,我們再加上__toString部分,如果出現字符串操作,那么進到tostring函數的追溯。

echo $this->a ==> 任意類的__toSring

image-20210205105639061

到目前為止,整個工具的大體架構就確定下來了。為了更好的確認每種會觸發魔術方法的方式。我們直接將所有的語法結構分類。

比如MethodCall(方法調用)就有可能觸發__call,而Assignment(賦值操作)的左值可能觸發__set,右值可能觸發__set。建立在這個基礎上,我們圈定了每種分類可能觸發的魔術方法順序以及范圍,落成代碼就成了已有的工具框架。

最后一個需要確定的問題是,如何界定是否存在危害?

這里我用了,可控+參數數量一致+敏感函數3個限制來圈定范圍

    def check_danger_sink(self, node):
        """
        檢查當前節點是否調用了危險函數并可控
        :param node:
        :return:
        """
        self.danger_function = {'call_user_func': [0],
                                'call_user_func_array': [0, 1],
                                'eval': [0],
                                'system': [0],
                                'file_put_contents': [0, 1],
                                'create_function': [0, 1],
                                }

        self.indirect_danger_function = {
                                'array_map': [0],
                                'call_user_func_array': [0],
                                }

        if node.node_type == 'FunctionCall' and node.source_node in self.danger_function:
            sink_node = eval(node.sink_node)

            if len(sink_node) >= len(self.danger_function[node.source_node]):
                # 必須有更多參數
                for i in self.danger_function[node.source_node]:
                    if self.check_param_controllable(sink_node[i], node):
                        continue

                    return False
                return True

只有出現對應敏感函數,且存在相應的必選參數,且相應的必選參數可控才會被認定為有危害。

到這里為止,我們已經完成了一個看上去還不錯的工具雛形,接下來一起看看效果吧。

Joomla 3.9.2反序列化利用鏈

這里我們拿Joomla 3.9.2做范例,目前版本的工具可以掃描到幾個利用鏈。其中主要的一條利用鏈如下:

image-20210205124653395

可以跟進去看看代碼

image-20210205124734611

有興趣的朋友可以深入去看下這里的利用鏈,這是一個可以構造下去的利用鏈。

寫在最后

在研究基于.QL的白盒掃描方案過程中,我遇到了很多很難解決的困難,所以就生出了寫一個小工具探索一下試試看的想法。于是phpunserializechain就誕生了。在寫插件的過程中也切實體會到了許多有趣的問題,也完善了一個更完整的CodeDB生成方案。踐行了不少想法,具體代碼可以看

https://github.com/LoRexxar/Kunlun-M/tree/master/core/plugins/phpunserializechain

如果感興趣的朋友可以通過星鏈計劃聯系我,一起交流思路,最后提前祝新年快樂啦~


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1480/