CTF-Web-PHP反序列化
概念解釋
PHP 反序列化漏洞又叫做 PHP 物件注入漏洞,我覺得這個表達很不直白,也不能說明根本的問題,不如我們叫他 PHP 物件的屬性篡改漏洞好了(別說這是我說的~~)
反序列化漏洞的成因在於程式碼中的 unserialize() 接收的引數可控,從上面的例子看,這個函式的引數是一個序列化的物件,而序列化的物件只含有物件的屬性,那我們就要利用對物件屬性的篡改實現最終的攻擊。
魔法方法
- construct(): 當物件建立時會自動呼叫(但在unserialize()時不會自動呼叫)
- wakeup(): unserialize()時會自動呼叫
- destruct(): 當物件被銷燬時會自動呼叫
- toString(): 當反序列化後的物件被輸出在模板中的時候(轉換成字串的時候)自動呼叫
- get(): 當從不可訪問的屬性讀取資料
- call(): 在物件上下文中呼叫不可訪問的方法時觸發
特別說明第四點:
_toString觸發條件較多,因此容易被忽略,常見的觸發條件如下:
(1)echo (
$obj
) / print($obj
) 列印時會觸發(2)反序列化物件與字串連線時
(3)反序列化物件參與格式化字串時
(4)反序列化物件與字串進行比較時(PHP進行比較的時候會轉換引數型別)
(5)反序列化物件參與格式化SQL語句,繫結引數時
(6)反序列化物件在經過php字串函式,如 strlen()、addslashes()時
(7)在in_array()方法中,第一個引數是反序列化物件,第二個引數的陣列中有toString返回的字串的時候toString會被呼叫
(8)反序列化的物件作為 class_exists() 的引數的時候
魔法方法的作用
反序列化了其他的類物件以後我們只是控制了是屬性,如果你沒有在完成反序列化後的程式碼中呼叫其他類物件的方法,我們還是束手無策,畢竟程式碼是人家寫的,人家本身就是要反序列化後呼叫該類的某個安全的方法,你總不能改人家的程式碼吧,但是沒關係,因為我們有魔法方法。
魔法正如上面介紹的,魔法方法的呼叫是在該類序列化或者反序列化的同時自動完成的,不需要人工干預,這就非常符合我們的想法,因此只要魔法方法中出現了一些我們能利用的函式,我們就能通過反序列化中對其物件屬性的操控來實現對這些函式的操控,進而達到我們發動攻擊的目的。
利用魔法方法發起攻擊
測試程式碼:
<?php
class K0rz3n {
private $test;
public $K0rz3n = "i am K0rz3n";
function __construct() {
$this->test = new L();
}
function __destruct() {
$this->test->action();
}
}
class L {
function action() {
echo "Welcome to XDSEC";
}
}
class Evil {
var $test2;
function action() {
eval($this->test2);
}
}
unserialize($_GET['test']);
我們先來分析一下這段程式碼,首先我們能看到 unserialize() 函式的引數我們是可以控制的,也就是說我們能通過這個介面反序列化任何類的物件(但只有在當前作用域的類才對我們有用),那我們看一下當前這三個類,我們看到後面兩個類反序列化以後對我們沒有任何意義,因為我們根本沒法呼叫其中的方法,但是第一個類就不一樣了,雖然我們也沒有什麼程式碼能實現呼叫其中的方法的,但是我們發現他有一個魔法函式 __destruct() ,這就非常有趣了,因為這個函式能在物件銷燬的時候自動呼叫,不用我們人工的干預,好,既然這樣我們就決定反序列化這個類的物件了,接下來讓我們看一下怎麼利用(我上面說過了,我們需要控制這個類的某些屬性,通過控制屬性實現我們的攻擊)
那我們看一下哪些屬性的控制是對我們有用的(這個時候我們就跳過了construct() 方法,畢竟他永遠不會被呼叫),因為這個例子比較簡單,destruct() 裡面只用到了一個屬性 test ,那肯定就是他了,那我們控制這個屬性為什麼內容我們就能攻擊了呢,我們再觀察一下 那些地方呼叫了 action() 函式,看看這個函式的呼叫中有沒有存在執行命令或者是其他我們能利用的點的,果然我們在 Evil 這個類中發現他的 action() 函式呼叫了 eval(),那我們的想法就很明確了,我們需要將 K0rz3n 這個類中的 test 屬性篡改為 Evil 這個類的物件,然後為了 eval 能執行命令,我們還要篡改 Evil 物件的 test2 屬性,將其改成我們的 Payload
分析完畢以後我們就可以構建我們的序列化字串了,構建的方法不是手寫(當然你願意我也不攔著你,理論上是可行的),我們要將這段程式碼複製一下,然後修改一些內容並進行序列化操作
生成payload
<?php
class K0rz3n {
private $test;
function __construct() {
$this->test = new Evil;
}
}
class Evil {
var $test2 = "phpinfo();";
}
$K0rz3n = new K0rz3n;
$data = serialize($K0rz3n);
file_put_contents("seria.txt", $data);
我們去除了一切與我們要篡改的屬性無關的內容,對其進行序列化操作,然後將序列化的結果複製出來,向剛剛的程式碼發起請求。可以看到我們攻擊成功,特別要提醒一下的就是我在圖中框起來的部分,上面說過由於是私有屬性,他有自己特殊的格式會在前後加兩個 %00 ,所以我們在傳輸過程中絕對不能忘掉。
反序列化漏洞方法小結
- 尋找unserialize()函式的引數是否有可控點
- 尋找反序列化目標,重點為wakeup()或destruct()魔法函式的類
- 一層一層的研究該類在魔法方法中使用的屬性和屬性呼叫的方法,看看是否有可控的屬效能實現在當前呼叫的過程中觸發的
- 找到要控制的屬性之後將要用到的程式碼部分複製下來,構造序列化,發起攻擊
To Be Continued