前提知識:
在php類中可能會存在一些叫做魔術函數(shù)(magic 函數(shù)),這些函數(shù)會在類進行某些事件的時候自動觸發(fā),例如__construct()會在一個對象被創(chuàng)建時調(diào)用,__destruct()會在一個對象銷毀時調(diào)用,__toString當對象被當做一個字符串的時候被調(diào)用。常見的魔術函數(shù)有__construct()、__destruct()、__toString()、__sleep()、__wakeup()。
舉例如下:
- <?php
- class test{<br>
- public $varr1="abc";
- public $varr2="123";
- public function echoP(){
- echo $this->varr1."<br>";
- }
- public function __construct(){
- echo "__construct<br>";
- }
- public function __destruct(){
- echo "__destruct<br>";
- }
- public function __toString(){
- return "__toString<br>";
- }
- public function __sleep(){
- echo "__sleep<br>";
- return array('varr1','varr2');
- }<br>
- public function __wakeup(){
- echo "__wakeup<br>";
- }
- } //Vevb.com
- $obj = new test(); //實例化對象,調(diào)用__construct()方法,輸出__construct<br>
- $obj->echoP(); //調(diào)用echoP()方法,輸出"abc"<br>
- echo $obj; //obj對象被當做字符串輸出,調(diào)用__toString()方法,輸出__toString<br>
- $s =serialize($obj); //obj對象被序列化,調(diào)用__sleep()方法,輸出__sleep<br>
- echo unserialize($s); //$s首先會被反序列化,會調(diào)用__wake()方法,被反序列化出來的對象又被當做字符串,就會調(diào)用_toString()方法。<br>
- // 腳本結束又會調(diào)用__destruct()方法,輸出__destruct<br>
- ?>
原理:
為什么會用到序列話這樣的方法?主要就是就是方便進行數(shù)據(jù)的傳輸,并且數(shù)據(jù)恢復之后,數(shù)據(jù)的屬性還不會發(fā)生變化。例如,將一個對象反序列化之后,還是保存了這個對象的所有的信息。同時還可以將序列化的值保存在文件中,這樣需要用的時候就可以直接從文件中讀取數(shù)據(jù)然后進行反序列化就可以了。在PHP使用serialize()和unserialize()來進行序列化和反序列化的。
而序列化的危害就在于如果序列化的內(nèi)容是用戶可控的,那么用戶就可以注入精心構造的payload。當進行發(fā)序列化的時候就有可能會出發(fā)對象中的一些魔術方法,造成意想不到的危害。
對象注入:
本質(zhì)上serialize()和unserialize()在PHP內(nèi)部實現(xiàn)上是沒有漏洞的,漏洞的主要產(chǎn)生是由于應用程序在處理對象、魔術函數(shù)以及序列化相關問題的時候?qū)е碌摹?/p>
如果在一個程序中,一個類用于臨時將日志存儲進某個文件中,當__destruct()方法被調(diào)用時,日志文件被刪除。代碼大致如下:
logfile.php
- <?php<br>
- class LogClass {
- public $logfilename = "";
- public function logdata($text) {
- echo "log data".$text."<br/>";
- file_put_contents($this->logfilename,$text,FILE_APPEBD);
- }
- public function __destruct() {
- echo 'deletes'.$this->logfilename;
- unlink(dirname(__FILE__).'/'.$this->logfilename);
- }<br>
- }<br>
- ?>
在其他類中使用LogClass
logLogin.php
- <?php
- <a href="/tags.php/include/" target="_blank">include</a> "index.php";
- $obj = new LogClass();
- $obj->logfilename = "login.log";
- $obj->logdata('記錄日志');
- ?>
上面的這段代碼就是一個正常的使用LogClass類來完成日志記錄的功能。
下面顯示的是存在對象注入漏洞的使用例子。
news.php
- <?php
- include "logfile.php";
- // some codes the use the LogClass
- class User {
- public $age = 0;
- public $name = '';
- public function print_data() {
- echo "User".$this->name."is".$this->age."years old.<br/>";
- }
- }
- // 從用戶接受輸入發(fā)序列化為User對象
- $usr = unserialize($_GET["user"]);
- ?>
上面顯示的代碼使用了LogClass對象同時還會從用戶那里接受輸入進行發(fā)序列化轉(zhuǎn)化為一個User對象。
當我們提交如下的數(shù)據(jù):
news.php?user=O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John”;}
這樣的語句是可以正常使用的,也是程序員希望使用的方法。
但是如果提交的數(shù)據(jù)為:
news.php?user=O:8:"LogClass":1:{s:11:"logfilename";s:9:".htaccess";}
那么最后就會輸出delete .htaccess。
可以看到通過構造的數(shù)據(jù),導致執(zhí)行了LogClass中的__destruct()方法然后刪除了網(wǎng)站中重要的配置文件。
從上面這個例子也可以看出來,如果沒有嚴格控制用戶的輸入同時對用戶的輸入進行了反序列化的操作,那么就有可能會實現(xiàn)代碼執(zhí)行的漏洞。
注入點:
PHP對象注入一般在處在程序的邏輯上面。例如一個User類定義了__toString()用來進行格式化輸出,但是也存在File類定義了__toString()方法讀取文件內(nèi)容然后進行顯示,那么攻擊者就有可能通過User類的反序列化構造一個File類來讀取網(wǎng)站的配置文件。
user.php
- <?php
- class FileClass {
- public $filename = "error.log";
- public function __toString() {
- echo "filename發(fā)生了變化==>" . $this->filename ;
- return @file_get_contents($this->filename);
- }
- }
- class UserClass {
- public $age = 0;
- public $name = '';
- public function __toString() {
- return 'User '.$this->name." is ".$this->age.' years old. <br/>';
- }
- }
- $obj = unserialize($_GET['usr']);
- echo $obj; //調(diào)用obj的__toString()方法<br>
- ?>
正常情況下我們應該傳入UserClass序列化的字符串,例如user.php?usr=O:9:"UserClass":2:{s:3:"age";i:18;s:4:"name";s:3:"Tom";},頁面最后就會輸出User Tom is 18 years old.。這也是一個理想的使用方法。
深入分析PHP對象注入詳解
但是如果我們傳入的數(shù)據(jù)為user.php?usr=O:9:"FileClass":1:{s:8:"filename";s:10:"config.php";},頁面最后的輸出是filename發(fā)生了變化==>config.php,執(zhí)行了FileClass中的__toString()方法。
深入分析PHP對象注入詳解
這樣就可以讀取到config.php中的源代碼了。
漏洞挖掘:
這類洞一般都是很難挖掘的,雖然顯示看起來很簡單,但實際上需要的條件還是相當?shù)目量痰模艺覍ο笞⑷氲穆┒匆话愣际峭ㄟ^審計源代碼的方式來進行尋找,看unserialize()的參數(shù)是否是可控的,是否存在反序列化其他參數(shù)對象的可能。
防御:
要對程序中的各種邊界條件進行測試
避免用戶對于unserialize()參數(shù)是可控的,可以考慮使用json_decode方法來進行傳參。
serialize — Generates a storable representation of a value
serialize — 產(chǎn)生一個可存儲的值的表示
unserialize — Creates a PHP value from a stored representation
unserialize — 從已存儲的表示中創(chuàng)建 PHP 的值
- <?php
- //聲明一個類
- class dog {
- var $name;
- var $age;
- var $owner;
- function dog($in_name="unnamed",$in_age="0",$in_owner="unknown") {
- $this->name = $in_name;
- $this->age = $in_age;
- $this->owner = $in_owner;
- }
- function getage() {
- return ($this->age * 365);
- }
- function getowner() {
- return ($this->owner);
- }
- function getname() {
- return ($this->name);
- }
- }
- //實例化這個類
- $ourfirstdog = new dog("Rover",12,"Lisa and Graham");
- //用serialize函數(shù)將這個實例轉(zhuǎn)化為一個序列化的字符串
- $dogdisc = serialize($ourfirstdog);
- print $dogdisc; //$ourfirstdog 已經(jīng)序列化為字符串 O:3:"dog":3:{s:4:"name";s:5:"Rover";s:3:"age";i:12;s:5:"owner";s:15:"Lisa and Graham";}
- print '<BR>';
- /*
- -----------------------------------------------------------------------------------------
- 在這里你可以將字符串 $dogdisc 存儲到任何地方如 session,cookie,數(shù)據(jù)庫,php文件
- -----------------------------------------------------------------------------------------
- */
- //我們在此注銷這個類
- unset($ourfirstdog);
- /* 還原操作 */
- /*
- -----------------------------------------------------------------------------------------
- 在這里將字符串 $dogdisc 從你存儲的地方讀出來如 session,cookie,數(shù)據(jù)庫,php文件
- -----------------------------------------------------------------------------------------
- */
- //我們在這里用 unserialize() 還原已經(jīng)序列化的對象
- $pet = unserialize($dogdisc); //此時的 $pet 已經(jīng)是前面的 $ourfirstdog 對象了
- //獲得年齡和名字屬性
- $old = $pet->getage();
- $name = $pet->getname();
- //這個類此時無需實例化可以繼續(xù)使用,而且屬性和值都是保持在序列化之前的狀態(tài)
- print "Our first dog is called $name and is $old days old<br>";
- print '<BR>';
- ?>
新聞熱點
疑難解答