前言:
什麼是php序列化和反序列化?
簡單的理解:序列化就是使用serialize()
將對象用字符串的方式進行表示並賦值給變量,反序列化是使用unserialize()
將序列化的後字符串(這裏的的字符串就是對象序列化之後的產物)構成相應的對象,反序列化是序列化的逆過程。
- 序列化
serialize()
操作:
serialize() 返回字符串,此字符串包含了表示 value 的字節流,可以存儲於任何地方。
簡單來講,就是將對象轉化爲可以傳輸的字符串,字符串中存儲着對象的變量、類型等。
舉個例子:
test.php
<?php
class people{
public $name = "hello";
public $age = "20";
}
$fairy = new people(); //實例化people對象
echo serialize($fairy); // 通過echo的方式輸出people序列化後的字符串
?>
最終瀏覽器輸出:
這裏來解釋下是什麼意思:首先O
表示的是一個對象,如果傳給serialize()
的值是一個數組,那麼這裏的O
就會是A
,後面的6
表示當前對象名是6個字符
people
表示對象名,後面的2
表示當前對象中存在2個屬性值
, {}裏面第一個s
表示的屬性名類型是String類型,第二個4
表示屬性名name的字符是4個
,以此類推第二個s
表示name的值是String類型
,後面的5表示name值hello的字符是5個
,後面的s:3:“age”;s:2:“20”;的意思也是和上面一樣。
- 反序列化unserialize ()操作:
將序列化後的字符串轉化爲PHP的值。
舉個例子:
test.php
<?php
class people{
public $name = "hello";
public $age = "20";
}
$fairy = new people();
$s_fairy = serialize($fairy); //序列化爲字符串
$uns_fairy = unserialize($s_fairy); //將字符串$s_fairy進行反序列化爲對象
var_dump($uns_fairy); # 打印對象
最終瀏覽器輸出:
魔術方法:
PHP 將所有以 __(兩個下劃線)開頭的類方法保留爲魔術方法。所以在定義類方法時,除了上述魔術方法,建議不要以 __ 爲前綴。
__construct()
,__destruct()
,__call()
,__callStatic()
,__get()
, __set()
, __isset()
,__unset()
,__sleep()
,__wakeup()
,__toString()
,__invoke()
,__set_state()
,__clone()
和__debugInfo()
等方法在 PHP 中被稱爲"魔術方法
"(Magic methods)。
這裏舉例幾個常用的魔術方法:
-
__construct()
PHP 5 允行開發者在一個類中定義一個方法作爲構造函數。具有構造函數的類會在每次創建新對象時先調用此方法,所以非常適合在使用對象之前做一些初始化工作。 -
__destruct()
析構函數會在到某個對象的所有引用都被刪除或者當對象被顯式銷燬時執行。 -
__sleep()
serialize() 函數會檢查類中是否存在一個魔術方法 __sleep()。如果存在,該方法會先被調用,然後才執行序列化操作。 -
__wakeup()
unserialize() 會檢查是否存在一個 __wakeup() 方法。如果存在,則會先調用 __wakeup 方法,預先準備對象需要的資源。 -
__toString()
__toString() 方法用於一個類被當成字符串時應怎樣迴應。例如 echo $obj; 應該顯示些什麼。
以上幾個常用的魔術方法使用順序和使用方法還請一定要牢記主!!!
反序列化漏洞:
__wakeup()的利用場景:
test.php
<?php
class Test{
var $num= "123";
function __wakeup(){
$fp = fopen("test.php", 'w');
fwrite($fp, $this -> num);
fclose($fp);
}
}
$test1 = $_GET['test'];
print_r($test1);
echo "<br />";
$seri = unserialize($test1);
require "test.php";
?>
以上代碼的意思就是,首先定義一個Test類
,類成員有$num=123
,並且存在__wakeup()
魔術方法,該方法的功能是打開一個test.php
文件,並且將Test類
中的變量$num
寫入到test.php
中,然後通過GET的方式傳入參數test
賦值給$test1
,最終將$test1
傳入unserialize()函數來進行反序列化操作。那麼這裏如果沒有對傳入unserialize()函數的值$test1進行過濾
的話,就會導致反序列化漏洞。
exp如下:
O:4:"Test":1:{s:3:"num";s:18:"<?php%20phpinfo();?>";}
此時就會生成test.php
文件,並且內容爲<?php phpinfo();?>
。
當然了這裏只是演示了當存在__wakeup()魔術方法的利用方式,除了該魔術方法的利用,php反序列化中其他魔術方法的利用也是非常多的,這裏就不一一舉例了。
__wakeup()魔術方法繞過(CVE-2016-7124)
-
漏洞影響版本:
PHP5 < 5.6.25
PHP7 < 7.0.10 -
漏洞產生原因:
如果存在__wakeup
方法,調用unserilize()
方法前則先調用__wakeup
方法,但是序列化字符串中表示對象屬性個數的值大於 真實的屬性個數時會跳過__wakeup的執行
-
漏洞復現
test.php
<?php
class car{ //定義car類
public $name ="benchi"; //$name屬性
function __wakeup(){
echo "this is __wakeup"."<br/>";
}
function __destruct(){
echo "this is __destruct"."<br/>";
}
}
$str=$_GET['s']; //GET方式接收參數賦值到$str
$un_str=unserialize($str); //將字符串進行反序列化,那麼我們傳入的字符串$str就要是序列化之後的值
echo $un_str->name."<br/>";
?>
腳本通過GET
方式傳入參數s賦值給$str
,對其反序列化後輸出name屬性的值
爲了方便觀察,我將傳入的s參數的name屬性值更改爲xss代碼
頁面顯示語句代表反序列化之前先調用了__wakeup 方法
點擊確定後,頁面完成後自動執行__destruct方法
將傳入的序列化數據的對象變量個數由1更改爲2,頁面只執行了__destruct方法,而且輸出name屬性時報錯,是由於反序列化數據時失敗無法創建對象。所以序列化字符串中表示對象屬性個數的值大於 真實的屬性個數時就會跳過__wakeup的執行
而直接執行__destruct方法。
總結:
通過上面的實驗可以看出,不管是反序列化漏洞還是CVE-2016-7124漏洞的利用,最根本的還是通過unserialize()函數來進行反序列化時,沒有對傳入的參數進行過濾所導致的。只要記住一點,所以由用戶輸入的值都是不可靠的,經過合理的過濾和處理,也就可以很好的預防此類漏洞的產生。