当前位置:网站首页>【安全攻防】序列化與反序列,你了解多少?

【安全攻防】序列化與反序列,你了解多少?

2022-07-07 03:35:00 大安全家

1.序列化與反序列化

首先要了解序列化與反序列化的定義,以及序列化反序列化所用到的基本函數。

序列化:把對象轉換為字節序列的過程稱為對象的序列化,相當於遊戲中的存檔。

PHP中的序列化函數serialize()

**serialize()**函數用於序列化對象數組,並返回一個字符串。serialize()函數序列化對象後,可以很方便的將它傳遞給其他需要它的地方,且其類型和結構不會改變

語法string serialize ( mixed $value )
參數說明$value: 要序列化的對象或數組。
返回值返回一個字符串。

示例:

<?php
highlight_file(__FILE__);
$sites=array('I', 'Like', 'PHP');
echo'<br/>';
var_dump(serialize($sites));    //把這個對象進行序列化
echo'<br/>';
​
classman{
public$name="xiaocui";
public$sex="man";
private$age=26;
}
$M=newman();//創建一個對象
var_dump(serialize($M)); //把這個對象進行序列化
​
?>

輸出結果為:

string(47) "a:3:{i:0;s:1:"I";i:1;s:4:"Like";i:2;s:3:"PHP";}"
string(79) "O:3:"man":3:{s:4:"name";s:7:"xiaocui";s:3:"sex";s:3:"man";s:8:"manage";i:26;}"

參數說明:

數組的序列化:
a 代錶一數組  
3 代錶數組中有3個元素
i 代錶數組的下標
0 代錶I元素的下標值
s 代錶元素I的數據類型為字符型
1 代錶元素I的長度為1
對象的序列化:
O 代錶是一個對象
3 代錶類名man的長度
3 代錶類中的字段數
s 代錶屬性name的類型為字符型
4 代錶屬性name的長度

//後面的以此類推,序列化字符串中字段內容以{開始,;}結束

反序列化:把字節序列恢複為對象的過程稱為對象的反序列化,相當於遊戲中的讀檔。

【一一幫助安全學習,所有資源獲取處一一】
①網絡安全學習路線
②20份滲透測試電子書
③安全攻防357頁筆記
④50份安全攻防面試指南
⑤安全紅隊滲透工具包
⑥信息收集80條搜索語法
⑦100個漏洞實戰案例
⑧安全大廠內部視頻資源
⑨曆年CTF奪旗賽題解析

PHP中的反序列化函數unserialize()

**unserialize()**函數用於將序列化後的字符串在還原為原來的數組或對象的過程。**unserialize()**函數可以快速將序列化的字符串還原出來,用來完成它本來的功能。

語法mixed unserialize ( string $str )
參數說明$str: 序列化後的字符串。
返回值返回的是轉換之後的值,可為 integer、float、string、array 或 object。不可反序列化時返回FALSE,並拋出提醒。

示例代碼:

<?php
highlight_file(__FILE__);
$sites=array('I', 'Like', 'PHP');
echo'<br/>';
echo$ser=serialize($sites).'<br/>';    //把這個對象進行序列化
var_dump(unserialize($ser)); //把序列化的字符串進行反序列化
echo'<br/>';
classman{
public$name="xiaocui";
public$sex="man";
private$age=26;
}
$M=newman();//創建一個對象
echo$ser=serialize($M).'<br/>'; //把這個對象進行序列化
var_dump(unserialize($ser));   //把序列化的字符串進行反序列化
​
?>

輸出結果為:

a:3:{i:0;s:1:"I";i:1;s:4:"Like";i:2;s:3:"PHP";}
array(3) { [0]=>string(1) "I"[1]=>string(4) "Like"[2]=>string(3) "PHP"}
​
O:3:"man":3:{s:4:"name";s:7:"xiaocui";s:3:"sex";s:3:"man";s:8:"manage";i:26;}
object(man)#2 (3) { ["name"]=> string(7) "xiaocui" ["sex"]=> string(3) "man" ["age":"man":private]=> int(26) } 

可以看出上述反序列化後的數組或對象,與原數據沒有任何變化。

序列化與反序列化在系統中的作用

①把對象的字節序列永久放在磁盤中,需要時可以隨時調用,大大節省磁盤占用空間。

②在傳輸過程中可以直接傳輸字節序列,而不是對象,這可以大大提高傳輸速率。

在業務系統中,需要對一些對象進行序列化存儲,讓他們離開內存空間,存放在一個文件中,以便持久化保存。例如:在用戶注册並登錄系統時,會將用戶信息如用戶名,密碼,cookie等信息先通過序列化存儲起來,當用戶再次登錄時將序列化後的字節序列通過反序列化成原對象到內存進行使用,可以大大節省內存開銷。

2.魔術方法

PHP將所有以__(兩個下劃線)開頭的類方法保留為魔術方法。所以在定義類方法時,除了上述魔術方法,建議不要以__為前綴。

PHP中常見魔術方法

__construct()

具有 __construct函數的類會在每次創建新對象時先調用此方法,適合在使用對象之前做一些初始化工作。

代碼示例:

<?php
highlight_file(__FILE__);
classdemo{
public$name="xiaocui";
public$sex="man";
private$age=26;
publicfunction__construct()
{
echo"<br/>"."類被實例化時調用我!";
}
}
$D=newdemo();   //實例化對象
​
?>

輸出結果:

類被實例化時調用我!

__destruct()

該函數會在到某個對象的所有引用都被删除或者當對象被銷毀時執行

代碼示例:

<?php
highlight_file(__FILE__);
class demo{
public $name="xiaocui";
public $sex="man";
private $age=26;
public function __construct()
{
echo "<br/>"."類被實例化時調用我!"."<br/>";
}
public function num($a,$b){
echo $c = $a + $b.'<br/>';
return $c;
}
public function __destruct(){
echo "當類中的所有方法都被銷毀時調用我!";
}
public function person($per){
echo "We are $per !!!".'<br/>';
}
}
$D=new demo();   //實例化對象
$D->num(5,6);   //調用num()方法
$D->person(man);  //調用person()方法
?>

輸出結果:

類被實例化時調用我!
11
Weareman!!!
當類中的所有方法都被銷毀時調用我!

上述輸出結果可以很直觀的看到代碼的執行順序:__construct()=>num(5,6)=>person(nanren)=>__destruct()

實例化對象時首先要執行構造方法__construct(),然後接著執行類中的實例num()person(),當所有方法都執行完成銷毀時,最後調用析構方法__destruct()

__wakeup()

在使用unserialize()時,會檢查是否存在一個__wakeup()魔術方法。如果存在,則該方法會先被調用,預先准備對象需要的資源。

代碼示例:

<?php
highlight_file(__FILE__);
classdemo{
public$name="xiaocui";
protected$sex="man";
private$age=26;
publicfunction__construct()
{
echo"<br/>"."類被實例化時調用我!"."<br/>";
}
publicfunction__destruct(){
echo"<br/>"."當類中的所有方法都被銷毀時調用我!"."<br/>";
}
publicfunction__wakeup()
{
echo"<br/>"."當反序列時首先調用我 !".'<br/>';
}
}
$D=newdemo();   //實例化對象
echo$ser=serialize($D);      //序列化對象$D
var_dump(unserialize($ser));    //反序列化字符串$ser
​
?>

輸出結果:

由於$age為私有屬性,在序列化時會在前後加空白字符%00,原本的字符長度為7,而在兩側加入空白字符則字符長度就會變為9

類被實例化時調用我!
O:4:"demo":3:{s:4:"name";s:7:"xiaocui";s:6:"*sex";s:3:"man";s:9:"demoage";i:26;}
當反序列時調用我 !
object(demo)#2
 (3) { ["name"]=> string(7) "xiaocui" ["sex":protected]=> 
string(3) "man" ["age":"demo":private]=> int(26) }
當類中的所有方法都被銷毀時調用我!
​
當類中的所有方法都被銷毀時調用我!

上述輸出結果可以很直觀的看到代碼的執行順序:__construct()=>serialize($D)=>__wakeup()=>unserialize($ser)=>__destruct()=>__destruct()

實例化對象時首先要執行構造方法__construct(),然後執行序列化serialize($D),然後再反序列化之前要執行__wakeup()方法,然後執行反序列化unserialize($ser),當所有方法都執行完成銷毀時最後執行__destruct()析構方法,由於反序列化後將原來的序列化字符串還原又執行一次__destruct()

__toString()

__toString()方法用於定義一個類被當成字符串時該如何處理。

示例代碼:

<?php
highlight_file(__FILE__);
class demo{
    public $name="xiaocui";
    protected $sex="man";
    private $age=26;
    public function __construct()
    {
        echo "<br/>"."類被實例化時調用我!"."<br/>";
    }
    public function __destruct(){
        echo "<br/>"."當類中的所有方法都被銷毀時調用我!"."<br/>";
    }
    public function __wakeup()
    {
        echo "<br/>"."當反序列時調用我 !".'<br/>';
    }
    public function __toString(){
        return "<br/>"."類被當成字符串處理時調用我!"."<br/>";
    }
}
$D=new demo();   //實例化對象
echo $D;   //類被當成字符串輸出

?>

輸出結果:

類被實例化時調用我!
​
類被當成字符串處理時調用我!
​
當類中的所有方法都被銷毀時調用我!

上述輸出結果可以看到,當類被當成字符串echo時,__toString()方法被調用並執行。

如果該類中沒有__toString()方法,進行echo輸出時,PHP會拋出致命性錯誤,錯誤如下:

Catchablefatalerror: ObjectofclassdemocouldnotbeconvertedtostringinD:\XXXX\phpstudy_pro\WWW\two\demo.phponline30

__sleep()

在使用serialize()函數時,程序會檢查類中是否存在一個__sleep()魔術方法。如果存在,則該方法會先被調用,然後再執行序列化操作。

__sleep()方法常用於提交未提交的數據,或類似的清理操作。同時,如果有一些很大的對象,但不需要全部保存,該函數就起到了很好的清理作用。

示例代碼:

<?php
highlight_file(__FILE__);
class demo{
    public $name="xiaocui";
    protected $sex="man";
    private $age=26;
    public function __construct()
    {
        echo "<br/>"."類被實例化時調用我!"."<br/>";
    }
    public function __destruct(){
        echo "<br/>"."當類中的所有方法都被銷毀時調用我!"."<br/>";
    }
    public function __wakeup()
    {
        echo "<br/>"."當反序列時調用我 !".'<br/>';
    }
    public function __sleep(){
        echo "<br/>"."當序列時調用我 !".'<br/>';
        return array("name","sex","age");    //這裏必須返回一個數值,裏邊的元素錶示返回的屬性名稱
    }

}
$D=new demo();   //實例化對象

echo $ser = serialize($D);  //序列化對象

?>

輸出結果:

類被實例化時調用我!
當序列時調用我!
O:4:"demo":3:{s:4:"name";s:7:"xiaocui";s:6:"*sex";s:3:"man";s:9:"demoage";i:26;}
當類中的所有方法都被銷毀時調用我!

上述輸出結果可以看到,當類被序列化時首先調用了__sleep()方法,該函數必須返回一個數值。如果該函數沒有返回屬性的話,序列化時會將屬性清空。

__invoke()

當嘗試以調用函數的方式調用一個對象時,__invoke方法會被自動調用。(本特性只在 PHP 5.3.0 及以上版本有效。)

代碼示例:

<?php
highlight_file(__FILE__);
class demo{
    public $name="xiaocui";
    protected $sex="man";
    private $age=26;
    public function __construct()
    {
        echo "<br/>"."類被實例化時調用我!"."<br/>";
    }
    public function __destruct(){
        echo "<br/>"."當類中的所有方法都被銷毀時調用我!"."<br/>";
    }
    public function __wakeup()
    {
        echo "<br/>"."當反序列化時調用我 !".'<br/>';
    }
    public function __sleep(){
        echo "<br/>"."當序列化時調用我 !".'<br/>';
        return array("name","sex","age");

    }
    public function __invoke()
    {
		echo "<br/>"."當以函數的方式調用對象時會調用我!".'<br/>';
    }

}
$D=new demo();   //實例化對象

$D();    //以函數的方式調用對象
?>

結果輸出:

類被實例化時調用我!
​
當以函數的方式調用對象時會調用我!
​
當類中的所有方法都被銷毀時調用我!

上述結果可以看到當使用函數的方法調用對象時,就會調用__invoke()方法.

如果沒有類中沒有該方法,那麼PHP會報出致命性錯誤,如下:

Fatalerror:
 UncaughtError: 
FunctionnamemustbeastringinD:\xxxxx\phpstudy_pro\WWW\two\demo.php:42Stacktrace:
 #0 {main} thrown in D:\xxxxx\phpstudy_pro\WWW\two\demo.php on line 42

__call()

在對象中調用一個不存在或者不可訪問方法時,__call會被調用。

代碼示例:

<?php
highlight_file(__FILE__);
class demo{
    public $name="xiaocui";
    protected $sex="man";
    private $age=26;
    public function __construct()
    {
        echo "<br/>"."類被實例化時調用我!"."<br/>";
    }
    public function num($a,$b){
        echo "<br/>".$c = $a + $b.'<br/>';
        return $c;
    }
    public function __destruct(){
        echo "<br/>"."當類中的所有方法都被銷毀時調用我!"."<br/>";
    }
    public function person($per){
        echo "<br/>"."We are $per !!!".'<br/>';
    }
    public function __wakeup()
    {
        echo "<br/>"."當反序列化時調用我 !".'<br/>';
    }
    public function __sleep(){
        echo "<br/>"."當序列時調用我 !".'<br/>';
        return array("name","sex","age");

    }
    public function __call($arg1,$arg2){
        echo  "<br/>"."當對象調用一個不存在或者不可訪問的方法時調用我!".'<br/>';
    }

}
$D=new demo();   //實例化對象
$D->num1(1,2);   //調用一個不存在的方法
?>

結果輸出:

類被實例化時調用我!
​
當對象調用一個不存在或者不可訪問的方法時調用我!
​
當類中的所有方法都被銷毀時調用我!

__set()

給不可訪問屬性賦值時,__set會被調用。

代碼示例:

<?php
highlight_file(__FILE__);
class demo extends demo1{
    public $name="xiaocui";
    protected $sex="man";
    private $age=26;
    public function __construct()
    {
        echo "<br/>"."類被實例化時調用我!"."<br/>";
    }
    public function __destruct(){
        echo "<br/>"."當類中的所有方法都被銷毀時調用我!"."<br/>";
    }
    public function person($per){
        echo "<br/>"."We are $per !!!".'<br/>';
    }
    public function __set($arg1,$arg2){

        echo "<br/>"."當給不存在或者不可訪問的屬性賦值時調用我!"."<br/>";

    }
}

class demo1{
    private $weight;
    public  $height;
    public function people(){
        echo $this->weight;
        echo $this->height;
    }
}

$D=new demo();   //實例化對象
$D->weight=74; //給不可訪問的屬性賦值
?>

輸出結果:

類被實例化時調用我!
當給不存在或者不可訪問的屬性賦值時調用我!
當類中的所有方法都被銷毀時調用我!

__isset()

對不可訪問屬性調用isset()empty()時,__iset()會被調用。

__unset()

對不可訪問屬性調用unset()時,__unset()會被調用。

__get()

讀取不可訪問屬性的值時,__get會被調用。

代碼示例:

<?php
highlight_file(__FILE__);
class demo extends demo1{
    public $name="xiaocui";
    protected $sex="man";
    private $age=26;
    public function __construct()
    {
        echo "<br/>"."類被實例化時調用我!"."<br/>";
    }

    public function __destruct(){
        echo "<br/>"."當類中的所有方法都被銷毀時調用我!"."<br/>";
    }

    public function __get($arg1){
        echo "<br/>"."讀取不存在或不可訪問的屬性時調用我!";
    }
}

class demo1{
    private $weight = 0;
    public  $height;
    public function people(){
        echo $this->weight;
        echo $this->height;
    }
}

$D=new demo();   //實例化對象

$D->weight;   //讀取父類中不可訪問的屬性
?>

輸出結果:

類被實例化時調用我!
讀取不存在或不可訪問的屬性時調用我!
當類中的所有方法都被銷毀時調用我!

3.反序列化漏洞

3.1 反序列化漏洞利用條件

① unserialize()函數中參數可控
② 存在可利用的類,且類中有魔術方法

代碼示例1:

<?php
highlight_file(__FILE__);

class demo
{

    public $arg1 = "0";

   public function __destruct()
    {

        echo $this->arg1;  //輸出用戶傳遞的arg1值

    }

}

$a=$_GET['arg'];     //接收前端傳遞的arg1變量
$unser = unserialize($a);    //反序列化傳遞的arg1
?>

上述的代碼滿足反序列化漏洞的兩個條件,arg參數可控,也就是unserialize()函數的參數可控;存在可利用的類demo,且demo類中有可利用的魔術方法__destruct(),然而__destruct()魔術方法中使用echo輸出變量了arg

通過arg參數,構造序列化字符串,通過unserialize()函數進行反序列化,最後通過__destruct()魔術方法輸出變量,造成XSS

image.png

1656063927_62b587b782d1ce26d9662.png

示例代碼2

<?php
highlight_file(__FILE__);

class demo
{

    public $arg1 = "0";

    public function __destruct()
    {

        eval($this->arg1);  //eval()去執行用戶傳遞的arg1值

    }

}

$a=$_GET['arg'];     //接收前端傳遞的arg1變量
$unser = unserialize($a);    //反序列化傳遞的arg1
var_dump($unser);
?>

如果魔術方法中存在eval(),且參數可控,那麼通過構造序列化字符串,通過反序列化漏洞造成RCE

1656063978_62b587ea396a1888f7509.png

3.2 __wakeup()函數繞過

在序列化時傳遞對象的屬性大於實際對象的屬性時,__wakeup()魔術方法將不會被執行,從而導致繞過。

PHP版本<=5.6.25或者PHP版本<=7.0.11

示例代碼:

<?php
highlight_file(__FILE__);

class demo
{

    public $arg1 = "0";

    public function __destruct()
    {

       eval($this->arg1);  //eval()去執行用戶傳遞的arg1值

    }

    public function __wakeup(){
        foreach(get_object_vars($this) as $k => $v) {
                $this->$k = '';          //將傳入的參數遍曆,全部賦值為空
            }

    }

}

$a=$_GET['arg'];     //接收前端傳遞的arg1變量
$unser = unserialize($a);    //反序列化傳遞的arg1
var_dump($unser);
?>

如果屬性值與對象實際屬性值相同,則會在反序列化時執行__wakeup()函數將傳入的變量值替換為空。
1656064048_62b58830a44fdeca601a9.png

如果屬性值設置大於實際對象屬性值則會繞過__wakeup()函數。
1656064071_62b588476b877e1951bf6.png

原网站

版权声明
本文为[大安全家]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/188/202207062028135059.html