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鏈的功能已經被其他師傅實現了,在此就不班門弄斧了,直接拿現成的來用。
按照個人理解反序列化入口點一般為__wakeup
、 __destruct
、 __construct
等
思路其實就是尋找__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
class User{
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
private $id;
public function __construct($username, $nickname, $password)
{
$this->username = $username;
$this->nickname = $nickname;
}
// 定義反序列化后調用的方法
public function __wakeup()
{
$this->password = '1234';
}
public function __destruct(){
$this->id = '123';
}
}
$user = new User('uusama', 'uu', '12345');
$ser= serialize($user);
var_dump(unserialize($ser));
結果:
object(User)[2]
public 'username' => string 'uusama' (length=6)
public 'nickname' => string 'uu' (length=2)
private 'password' => string '1234' (length=4)
private 'id' => null
在反序列化中一般開發可能會在__wakeup
中做一些過濾。
參考
如何自動化挖掘php反序列化鏈 - phpunserializechain誕生記
結尾
但該工具並沒有達到我個人的預期,因為該工具中只是使用__destruct
這單個方法作為反序列化的入口點。