PHP審計之POP鏈挖掘

nice_0e3發表於2021-10-12

PHP審計之POP鏈挖掘

前言

續上文中的php反序列化,繼續來看,這個POP的挖掘思路。在其中一直構思基於AST去自動化挖掘POP鏈,迫於開發能力有限。沒有進展,隨後找到了一個別的師傅已經實現好的專案。

魔術方法

__wakeup() //使用unserialize時觸發
__sleep() //使用serialize時觸發
__destruct() //物件被銷燬時觸發
__call() //在物件上下文中呼叫不可訪問的方法時觸發
__callStatic() //在靜態上下文中呼叫不可訪問的方法時觸發
__get() //用於從不可訪問的屬性讀取資料
__set() //用於將資料寫入不可訪問的屬性
__isset() //在不可訪問的屬性上呼叫isset()或empty()觸發
__unset() //在不可訪問的屬性上使用unset()時觸發
__toString() //把類當作字串使用時觸發,file_exists()判斷也會觸發
__invoke() //當指令碼嘗試將物件呼叫為函式時觸發

__call__callstatic

現實情況下__call的利用居多,該魔術方法觸發的條件是在物件上下文中呼叫不可訪問的方法時觸發。

呼叫流程如下:

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

如果觸發__call方法,那麼a,即方法名,會作為__call的方法的第一個引數,而引數列表會作為__call的方法第二個引數。

來看到程式碼

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

這裡有2個利用路徑,一個是$this->a中構造一個存在方法的例項化類,另一種方式是找一個不存在b方法並且存在__call方法的類,當b不存在時,即自動呼叫__call

__callstatic方法只有在呼叫到靜態方法的時候才能觸發

__get__set

不存在該類變數或者不可訪問時,則會呼叫對應的__get方法

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

__get程式碼案例

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

呼叫不存在變數a,即會自動觸發__get方法,

資料寫入不可訪問的變數或不存在的變數即呼叫__set

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

__toString

把類當作字串使用時觸發

$this->_adapterName = $adapterName;
$adapterName = 'xxx' . $adapterName;

POP鏈挖掘

此前構思的自動化挖掘POP鏈的功能已經被其他師傅實現了,在此就不班門弄斧了,直接拿現成的來用。
思路其實尋找__destruct方法,作為入口點,然後尋找一個回撥函式作為末端。而中間需要尋找各種中間鏈,將其串聯起來。串聯的方法基本上就是一些魔術方法和一些自定義的方法。

專案地址:https://github.com/LoRexxar/Kunlun-M

cp Kunlun_M/settings.py.bak Kunlun_M/settings.py

python kunlun.py init initialize

python kunlun.py config load

python kunlun.py plugin php_unserialize_chain_tools -t C:\kyxscms-1.2.7

結果:

 [20:28:51] [PhpUnSerChain] New Source __destruct() in thinkphp#library#think#Process_php.Class-Process
 [20:28:51] thinkphp#library#think#Process_php.Class-Process
 newMethod                        Method-__destruct()
 [20:28:51] thinkphp#library#think#Process_php.Class-Process.Method-__destruct
 MethodCall                       Variable-$this->stop()
 [20:28:51] thinkphp#library#think#Process_php.Class-Process.Method-stop
 MethodCall                       Variable-$this->updateStatus('Constant-false',)
 [20:28:51] thinkphp#library#think#Process_php.Class-Process.Method-updateStatus
 MethodCall                       Variable-$this->readPipes('Variable-$blocking', '\ === Constant-DIRECTORY_SEPARATOR ? 627')
 [20:28:51] thinkphp#library#think#Process_php.Class-Process.Method-readPipes
 MethodCall                       Variable-$this->processPipes->readAndWrite('Variable-$blocking', 'Variable-$close')
 [20:28:51] thinkphp#library#think#console#Output_php.Class-Output
 newMethod                        Method-__call('$method', '$args')
 [20:28:51] thinkphp#library#think#console#Output_php.Class-Output.Method-__call.If
 FunctionCall                     call_user_func_array("Array-['Variable-$this', 'block']", 'Variable-$args')
 [20:28:51] [PhpUnSerChain] UnSerChain is available.

這其實利用鏈就清晰了

Process->__destruct ==>Process->stop ==>Process->updateStatus ==> Process->readPipes ==> Output->readAndWrite ==> Output->__call==> call_user_func_array()

參考

淺析 PHP 反序列化漏洞的利用與審計

如何自動化挖掘php反序列化鏈 - phpunserializechain誕生記

結尾

但該工具並沒有達到我個人的預期,因為該工具中只是使用__destruct這單個方法作為反序列化的入口點。

相關文章