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

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


咨詢
建站咨詢
