免責聲明由于傳播、利用本公眾號狐貍說安全所提供的信息而造成的任何直接或者間接的后果及損失,均由使用者本人負責,公眾號狐貍說安全及作者不為此承擔任何責任,一旦造成后果請自行承擔!如有侵權(quán)煩請告知,我們會立即刪除并致歉,謝謝! 0x01 前言 概念:這其實是為了解決 PHP 對象傳遞的一個問題,因為 PHP 文件在執(zhí)行結(jié)束以后就會將對象銷毀,那么如果下次有一個頁面恰好要用到剛剛銷毀的對象就會束手無策,總不能你永遠不讓它銷毀,等著你吧,于是人們就想出了一種能長久保存對象的方法,這就是 PHP 的序列化,那當我們下次要用的時候只要反序列化一下就 ok 啦 序列化的目的是方便數(shù)據(jù)的傳輸和存儲. json 是為了傳遞數(shù)據(jù)的方便性.。 0x02 序列化與反序列化 序列化:函數(shù) : serialize() 把復雜的數(shù)據(jù)類型壓縮到一個字符串中 數(shù)據(jù)類型可以是數(shù)組,字符串,對象等 序列化一個對象將會保存對象的所有變量,但是不會保存對象的方法,只會保存類的名字。 反序列化: 函數(shù): unserialize() 恢復原先被序列化的變量 0x03 魔術(shù)函數(shù) __construct 當一個對象創(chuàng)建時被調(diào)用,__destruct 當一個對象銷毀時被調(diào)用,__toString 當一個對象被當作一個字符串被調(diào)用。__wakeup() 使用unserialize時觸發(fā)__sleep() 使用serialize時觸發(fā)__destruct() 對象被銷毀時觸發(fā)__call() 在對象上下文中調(diào)用不可訪問的方法時觸發(fā)__callStatic() 在靜態(tài)上下文中調(diào)用不可訪問的方法時觸發(fā)__get() 用于從不可訪問的屬性讀取數(shù)據(jù)__set() 用于將數(shù)據(jù)寫入不可訪問的屬性__isset() 在不可訪問的屬性上調(diào)用isset()或empty()觸發(fā)__unset() 在不可訪問的屬性上使用unset()時觸發(fā)__toString() 把類當作字符串使用時觸發(fā),返回值需要為字符串__invoke() 當腳本嘗試將對象調(diào)用為函數(shù)時觸發(fā) 1.序列化 class people{ public $name = 'sam'; private $sex = 'man'; protected $age = '20'; } $people1 = new people(); $object = serialize($people); print_r($object);123456789101112131415 定義一個people類,含有公共屬性name,私有屬性sex,保護屬性age。隨后實例化一個people1,并對其進行序列化,最后輸出結(jié)果為: O:6:'people':3:{s:4:'name';s:3:'sam';s:11:' people sex';s:3:'man';s:6:' * age';s:2:'20';} ![]() 2.反序列化 public $name = 'sam'; private $sex = 'man'; protected $age = '20'; } $people1 = new people(); $object = serialize($people1);$a=unserialize($object);#print_r($object);var_dump($a);1234567891011121314151617 輸出為: object(people)#2 (3) { ['name']=> string(3) 'sam' ['sex':'people':private]=> string(3) 'man' ['age':protected]=> string(2) '20' } 這里需要說明一下,對于public的屬性無需其他特殊操作,但是對于private屬性,描述的時候需要在前后添加空格,或者帶上其所在的類名,對于protected屬性需要加' * '。 幾個魔法函數(shù)的調(diào)用 1. __wakeup() 當unserialize()函數(shù)反序列化時,在數(shù)據(jù)流還未被反序列化未對象之前會調(diào)用該函數(shù)進行初始化. 2. __destruct() 當對象銷毀時觸發(fā),也就是說只要你反序列化或者實例化一個對象,當你調(diào)用結(jié)束后都會觸發(fā)該函數(shù)。 結(jié)果:hi結(jié)束了 可以看到,先輸出了hi,說明wakeup()函數(shù)先被調(diào)用,隨后整個反序列化結(jié)束,對象被銷毀,觸發(fā)destruct()函數(shù),輸出結(jié)束了。 3. __toString() 當一個對象被當作字符串使用時觸發(fā)。 a; }}class next{ function __toString(){ echo '我在這'; }}$t='O:4:'star':1:{s:1:'a';O:4:'next':0:{}}';unserialize($t);1234567891011121314 結(jié)果:我在這 Catchable fatal error: Method next::__toString() must return a string value in /tmp/41bac5636b55eff5c8abea138d605489916c2612abc45fd39fdaa87a827a0e00/main.php on line 5 這里沒有retrun,也沒有忽略報錯,所有有一條報錯信息,無關(guān)緊要,但是要說的是,__toString()是要又return的,不然會報錯。結(jié)果顯示當類star中的 echo $this->a;1 執(zhí)行時,a被當作一個字符串,此時我將a設(shè)置為類next,此時類next作為字符串被調(diào)用,所以觸發(fā)類next中的toString()函數(shù),輸出“我在這”。4. invoke() 當類被當作函數(shù)調(diào)用時觸發(fā),看實例。 a; return $function(); }}class next{ function __invoke(){ echo '我在這'; }}$t='O:4:'star':1:{s:1:'a';O:4:'next':0:{}}';unserialize($t);123456789101112131415 結(jié)果:我在這 分析過程和上面那個函數(shù)一樣,也是通過反序列化給a賦值,只是賦的不是字符串而是其他類,然后 return $function();1的時候,將類當作函數(shù)調(diào)用,觸發(fā)了invoke()函數(shù)輸出了“我在這”。**5. get()** 這個函數(shù)是當訪問不可訪問的屬性的時候觸發(fā),不可訪問的屬性有兩種 私有屬性或者保護屬性,這種訪問受限的屬性的時候會觸發(fā)__get()屬性不存在的時候,也會觸發(fā)__get() str['str']->source; }}class next{ function __get($name){ echo '我在這'; return; }}$t='O:4:'star':2:{s:1:'a';N;s:3:'str';a:1:{s:3:'str';O:4:'next':0:{}}}';unserialize($t);123456789101112131415 結(jié)果:我在這 通過str['str’]賦值為類next,訪問next的source,但是類next中不存在屬性source所以觸發(fā)__get()函數(shù),訪問保護屬性等同理。 小結(jié):反序列化的過程通過這些魔法函數(shù)可以達到我們想到要的操作,尤其是后面3個函數(shù),大家會發(fā)現(xiàn),這三個函數(shù)可以達到多個類的連續(xù)使用,從而達到鏈的效果,這也就是反序列化中的pop鏈的編寫,接下來我們講一下反序列化的漏洞 0x04 反序列化漏洞1.__wakeup( )繞過(CVE-2016-7124) 反序列化時,如果表示對象屬性個數(shù)的值大于真實的屬性個數(shù)時就會跳過__wakeup( )的執(zhí)行。 影響版本:PHP before 5.6.25 7.x before 7.0.10 a; }}$t='O:4:'star':1:{s:1:'a';s:9:'我在這';}';unserialize($t);123456789 結(jié)果:我在這 O:4:'star':2:{s:1:'a';s:9:'我在這';}1 結(jié)果:無輸出 結(jié)果顯示,當表示屬性個數(shù)大于真實個數(shù)時,wakeup()函數(shù)不執(zhí)行,被繞過了,通常題目中,wake()中含有很多限制,通過這個漏洞繞過__wake()可以達到繞過限制的目的。 2.POP鏈構(gòu)造 POP鏈的構(gòu)造 首先認識一下什么是POP?POP面向?qū)傩跃幊?。指從現(xiàn)有運行環(huán)境中尋找一系列的代碼或指令調(diào)用,然后根據(jù)需求構(gòu)造出一組連續(xù)的調(diào)用鏈。其實就是構(gòu)造一條和原代碼需求一樣的鏈條,去找到被控制的屬性或方法,從而構(gòu)造POP鏈達到攻擊的目的。 直接上題方便理解 file_get($this->var); echo $content; }} class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo $this->source.'Welcome'.' '; } public function __toString(){ return $this->str['str']->source; } public function _show(){ if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) { die('hacker'); } else { highlight_file($this->source); } } public function __wakeup(){ if(preg_match('/gopher|http|file|ftp|https|dict|\.\./i', $this->source)) { echo 'hacker'; $this->source = 'index.php'; } }} class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); }} if(isset($_GET['hello'])){ unserialize($_GET['hello']);}else{ $show = new Show('pop3.php'); $show->_show();} 1.首先看到unserialize($_GET['hello’]) 將get傳參的hello進行了反序列化操作。那么將會調(diào)用到Show類中__weakup方法。2.因為 this->source = “index.php” source被當做字符串所以調(diào)用Show類中的__to string.3. ** return $this->str['str’]->source ** source屬性不存在所以調(diào)用Test類中的 get方法。4. ** $function = $this->p;return $function(); **把取出來的p當做還是調(diào)用因此又會引起調(diào)用了 Read類中的__invoke方法,其中就可以把文件讀取出來了。 exp $s = new Show();$t = new Test();$r = new Read();$t -> p = $r;$s ->str['str'] = $t;$s -> source = $s;var_dump(serialize($s)); 0x05 反序列化詳講 1.簡介 序列化就是將數(shù)據(jù)轉(zhuǎn)化成一種可逆的字符串,字符串還原原來結(jié)構(gòu)的過程叫做反序列化 序列化后,方便保存和傳輸(保留成員變量,不保留函數(shù)方法) 數(shù)據(jù)(對象)--------序列化---------->字符串-----------反序列化-------->數(shù)據(jù)(對象) 2.原理 函數(shù): serialize()序列化 將一個對象轉(zhuǎn)換成可以傳輸?shù)囊粋€字符串 序列化對象后,可以方便的將它傳遞到其他需要它的地方,且其類型和結(jié)構(gòu)不會改變 eg: class S{ public $test='pikachu'; } $s=new S(); //創(chuàng)建一個對象 serialize($s); //把這個對象進行序列化 unserialize()反序列化將序列化后的字符串還原成一個對象,或數(shù)組(即進行反序列化),并返回原始的對象結(jié)構(gòu)并在后面的代碼中繼續(xù)使用,加密后的字符串如下所示 ![]() 對字符串代碼進行分析: $u=unserialize('O:1:'S':1:{s:4:'test';s:7:'pikachu';}');echo $u->test; //得到的結(jié)果為pikachuO:1:'S':1:{s:4:'test';s:7:'pikachu';} //這是序列化結(jié)果O:代表object 1:代表對象名字長度為一個字符 S:對象名稱 1:代表對象里面有一個變量 s:數(shù)據(jù)類型 4:變量名長度 test:變量名稱 s:數(shù)據(jù)類型 7:變量值的長度 pikachu:變量值 常見的序列化格式: 二進制格式 字節(jié)數(shù)組 json字符串 xml字符串 ……布爾型(bool):b 整數(shù)型(int):i 字符串型(str):s 數(shù)組型(array):a 對象型(object):O NULL型:N 產(chǎn)生的原因: 對用戶的輸入檢測不嚴1.無類:當未檢測出攻擊者輸入的序列化字符串中包含的惡意執(zhí)行語句攻擊者從而達到控制反序列化過程,進而進行惡意代碼的執(zhí)行(好比SQL注入,目錄遍歷等操作)2.有類:當進行反序列化的時候就有可能會觸發(fā)對象中的一些魔術(shù)方法 魔術(shù)方法(觸發(fā)): (前提:有可利用的類) __construct() //創(chuàng)建對象時觸發(fā) __destruct() //對象銷毀時觸發(fā) __call() //在對象中調(diào)用不可訪問的方法時觸發(fā) __callStatic() //在靜態(tài)中調(diào)用不可訪問的方法時觸發(fā) __get() //用于從不可訪問的屬性讀取數(shù)據(jù) __set() //用于將數(shù)據(jù)寫入不可訪問的屬性 __isset() //在不可訪問的屬性上調(diào)用isset()或empty()觸發(fā) __unset() //在不可訪問的屬性上使用unset()時觸發(fā) __invoke() //當腳本嘗試將對象調(diào)用為函數(shù)時觸發(fā) __wakeup() //執(zhí)行unserialize()時,先會調(diào)用這個函數(shù) __sleep() //執(zhí)行serialize()時,先會調(diào)用這個函數(shù) 利用: 分析因為這是反序列化API所以要先把包含執(zhí)行語句的php序列化 ![]() 構(gòu)造序列化編寫包含惡意語句的php下面是一個彈窗 class S{ var $test = ' ';} $a = new S();echo serialize($a);?>構(gòu)造出來是: O:1:'S':1:{s:4:'test';s:29:'';}并將其進行序列化(網(wǎng)上的在線工具都可) ![]() 輸入執(zhí)行將序列化后的復制到輸入框提交 會產(chǎn)生彈窗 ![]() 0x06 JAVA反序列化和反序列化詳解1、 序列化和反序列化的必要性當兩個進程進行遠程通信時,可以相互發(fā)送各種類型的數(shù)據(jù),包括文本、圖片、音頻、視頻等, 而這些數(shù)據(jù)都會以二進制序列的形式在網(wǎng)絡(luò)上傳送。而JAVA其實是面向?qū)ο蟮拈_發(fā)方式,一切都是JAVA對象,想要實現(xiàn)JAVA對象的網(wǎng)絡(luò)傳輸,就可以使用序列化和反序列化來實現(xiàn)。發(fā)送方將需要發(fā)送的Java對象序列化轉(zhuǎn)換為字節(jié)序列,然后在網(wǎng)絡(luò)上傳送;接收方接收到字符序列后,使用反序列化從字節(jié)序列中恢復出Java對象。Java序列化的好處:一是實現(xiàn)了數(shù)據(jù)的持久化,通過序列化可以把數(shù)據(jù)永久地保存到硬盤上(通常存放在文件里);二是利用序列化實現(xiàn)遠程通信,即在網(wǎng)絡(luò)上傳送對象的字節(jié)序列??傊涸诰W(wǎng)絡(luò)中數(shù)據(jù)的傳輸必須是序列化形式來進行的。其他序列化的方式可以是json傳輸,xml形式傳輸。2、序列化和反序列化(1)含義:序列化就是內(nèi)存中的對象寫入到IO流中,保存的格式可以是二進制或者文本內(nèi)容。反序列化就是IO流還原成對象(2)用途: 傳輸網(wǎng)絡(luò)對象保存Session3、Java序列化演示(1)序列化java.io.ObjectOutputStream 代表對象輸出流,它的 writeObject()方法可對參數(shù)指定的對象進行序列化,把得到的字節(jié)序列寫到一 個目標輸出流中。(2)反序列化java.io.ObjectInputStream 代表對象輸入流,它的readObject()方法從一個源輸入流中讀取字節(jié)序列,再把它們反序列化為一個對 象,并將其返回。4、反序列化漏洞(1)如果某個類需要自定義反序列化方式,可以重寫類的 readObject() 方法(2)在反序列化的過程中,會調(diào)用這個類中的重寫的readObject()方法(3)如果readObject()方法的代碼有一些敏感操作,就可能會引發(fā)漏洞(或者自定義的反序列化方法)5、實現(xiàn)Java對象序列化與反序列化的方法如果有一個Demo類,它的對象需要序列化,提供如下三種方法:(1)方法一:若Demo類僅僅實現(xiàn)了Serializable接口,則可以按照以下方式進行序列化和反序列化。ObjectOutputStream采用默認的序列化方式,對Demo對象的非transient的實例變量進行序列化。ObjcetInputStream采用默認的反序列化方式,對對Demo對象的非transient的實例變量進行反序列化。(2)方法二:若Demo類僅僅實現(xiàn)了Serializable接口,并且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),則采用以下方式進行序列化與反序列化。ObjectOutputStream調(diào)用Demo對象的writeObject(ObjectOutputStream out)的方法進行序列化。ObjectInputStream會調(diào)用Demo對象的readObject(ObjectInputStream in)的方法進行反序列化。(3)方法三:若Demo類實現(xiàn)了Externalnalizable接口,且Demo類必須實現(xiàn)readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,則按照以下方式進行序列化與反序列化。ObjectOutputStream調(diào)用Demo對象的writeExternal(ObjectOutput out))的方法進行序列化。ObjectInputStream會調(diào)用Demo對象的readExternal(ObjectInput in)的方法進行反序列化。6、JDK類庫中序列化的步驟 步驟一:創(chuàng)建一個對象輸出流,它可以包裝一個其它類型的目標輸出流,如文件輸出流: ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“C:\\java_JDK\\objectfile.obj”)); 步驟二:通過對象輸出流的writeObject()方法寫對象: String obj1 = (String)in.readObject();Date obj2 = (Date)in.readObject();7、transient關(guān)鍵字transient關(guān)鍵字表示有理的,被修飾的數(shù)據(jù)是不能進行序列化的修改如下: private transient char sex; //被transient關(guān)鍵字修飾,不參與序列化 運行結(jié)果: 文件存在name = Tomsex = year = 20gpa = 3.6 此時可以看見,被transient關(guān)鍵字修飾的變量sex并沒有被序列化,返回了空值。 0x07 反序列化防御 治病需要除根,能從根本上阻止反序列化安全問題的防御方案就是完整性校驗,而最常見的例子之一就是JWT。 我們知道JWT由3部分組成:Header,Payload,Verify Signature。最后的簽名部分其實就是對數(shù)據(jù)進行完整性校驗的關(guān)鍵部分。 ![]() 圖:JWT基本結(jié)構(gòu) 服務(wù)器端在接受到JWT之后,首先用secret對數(shù)據(jù)部分進行哈希計算,隨后檢查計算出來的哈希值是否和請求中的JWT簽名部分的哈希值相同。若兩者一致則認為數(shù)據(jù)完整性沒有被破壞,若兩者有差異則說明數(shù)據(jù)被修改過。 如果攻擊者想要憑空偽造一個JWT,或者想修改JWT中的數(shù)據(jù),但由于計算哈希值的secret只有服務(wù)器端才知道,因此攻擊者無法偽造出合法的簽名字段,進而這樣有問題的JTW很容易就能被服務(wù)器端識別出來。 值得注意的是,完整性校驗還需要把數(shù)據(jù)結(jié)構(gòu)也包含進來,這是因為攻擊者可能會修改序列化后的數(shù)據(jù)的結(jié)構(gòu),而不僅僅只是數(shù)據(jù)。 其他防御措施 除此之外其他有助于防御反序列化安全問題的措施,但并不能完美的做到事前預防,例如: 反序列化之前,先進行嚴格的數(shù)據(jù)類型校驗。由于校驗規(guī)則容易被攻擊者探索出來,進而容易被繞過,因此防御不能僅依賴這一個手段,但可以作為完整性校驗防御方案的補充。對反序列化過程進行詳盡的日志記錄,用以安全審計或調(diào)查。監(jiān)控反序列化過程,在發(fā)現(xiàn)疑似反序列化攻擊時進行警報。 |
|