利用PHP的0day黑掉Pornhub並獲得2W美刀獎勵

暗網視界 發佈 2020-01-01T02:24:33+00:00

請求示例:這可以通過發送一個包含數組的特製反序列化對象來進一步驗證:HTTP響應:乍一看,這可能只是一個信息泄露漏洞,但眾所周知,在反序列化時使用用戶可控的輸入是會產生安全問題的:ROPin PHP applicationsShocking News in PHP Exploit

搜索公眾號:暗網黑客教程

可領全套安全課程、配套攻防靶場


一.漏洞發現

在分析了Pornhub使用的平台之後,我們在其網站上檢測到了unserialize函數的使用,其中的很多功能點(例如上傳圖片的地方等等)都受到了影響

例如下面兩個URL:

  • http://www.pornhub.com/album_upload/create
  • http://www.pornhub.com/uploading/photo

在所有情況下,都通過POST請求中名為cookie的參數傳遞反序列化數據,然再通過HTTP響應包的Set-Cookie頭反映出來。

請求示例:

這可以通過發送一個包含數組的特製反序列化對象來進一步驗證:

HTTP響應:

乍一看,這可能只是一個信息泄露漏洞,但眾所周知,在反序列化時使用用戶可控的輸入是會產生安全問題的:


ROP in PHP applications(https://www.owasp.org/images/9/9e/Utilizing-Code-Reuse-Or-Return-Oriented-Programming-In-PHP-Application-Exploits.pdf)


Shocking News in PHP Exploitation(https://www.nds.ruhr-uni-bochum.de/media/hfs/attachments/files/2010/03/hackpra09_fu_esser_php_exploits1.pdf)


常規的利用技術要使用所謂的Property-Oriented-Programming (POP)技術,這種技術利用已定義類的魔術方法來觸發惡意代碼執行。


不幸的是,我們很難收集有關Pornhub使用的框架和PHP對象的任何信息。

我們利用了多個常用框架中包含的類進行了測試,但都沒有成功。


問題描述

單獨的核心反序列化器相對複雜,因為它涉及PHP 5.6中的1200多行代碼。

此外,許多PHP內部類都有其自己的反序列化方法。

因為PHP支持諸如對象,數組,整數,字符串甚至引用之類的結構,所以其中包含很多邏輯錯誤和內存破壞漏洞就不足為奇了。


遺憾的是,由反序列化產生的漏洞在過去已經引起了很多關注(例如phpcodz),所以諸如PHP 5.6或PHP 7之類的更新版本的PHP中沒有這種類型漏洞的報告。


因此,審計它好比擠壓已經壓榨過的檸檬,經過如此多的關注和眾多的安全修復,潛在漏洞不應該已經全部被修復掉了嗎?



模糊測試unserialize函數

為了找到答案,Dario實現了一個模糊測試器,專門用於產生傳遞給unserialize函數的序列化字符串。

在PHP 7下運行模糊測試器會立即導致意外行為。

不過,在針對Pornhub的伺服器進行測試時,這種行為無法復現。

因此,我們假設Pornhub的伺服器使用的是PHP 5版本。


在對較新版本的PHP 5運行模糊測試器之後會生成了超過1 TB的日誌,但並沒有從中發現崩潰或者異常行為。

最終,在經過越來越多的努力之後,我們又偶然發現了意外行為。

此時我們必須搞清楚幾個問題:這些問題與安全性有關嗎?

我們只能在本地利用還是可以遠程利用這些漏洞?

為了覆蓋這些更加複雜的情況,模糊測試器生成了超過200 KB的不可列印數據塊。


分析意外行為

分析潛在問題需要大量時間。

最終,我們在這些產生的意外行為中發現了一個use-after-free(UAF)漏洞!經過進一步的調查,我們發現根本原因可以在PHP的垃圾回收算法中找到,這是一個與PHP反序列化完全無關的組件。

但是,這兩個組件的交互僅在反序列化完成其工作之後才發生。

因此,它不太適合遠程利用。經過進一步分析,對問題的根本原因有了更深入的了解,並進行了許多艱苦的工作,發現了類似的UAF漏洞,這對於遠程利用來說似乎很有希望:



PHP Bug – ID 72433CVE-2016-5771

https://bugs.php.net/bug.php?id=72433

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5771


PHP Bug – ID 72434CVE-2016-5773

https://bugs.php.net/bug.php?id=72434

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5773


發現的PHP漏洞及其發現方法的高度複雜性使得有必要撰寫單獨的文章。


您可以在Dario的模糊反序列化文章中閱讀更多詳細信息:
fuzzing unserialize write-up(https://www.evonide.com/fuzzing-unserialize)
此外,我們還寫了一篇有關破壞PHP的垃圾回收和反序列化的文章:
Breaking PHP’s Garbage Collection and Unserialize(https://www.evonide.com/breaking-phps-garbage-collection-and-unserialize/)


二.漏洞利用

即使這個有希望的UAF漏洞也很難利用。

特別是,它涉及多個開發階段。
由於我們的主要目標是執行任意代碼,因此我們需要以某種方式控制x86_64上稱為RIP的CPU指令指針。這通常涉及以下障礙:

1.棧和堆以及任何其他可寫段都被標記為不可執行

https://en.wikipedia.org/wiki/Executable_space_protection


2.即使能夠控制指令指針(RIP),但也需要知道要執行的內容,即需要獲得可執行內存段的有效地址。

在PHP上下文中,通常使用zend_eval_string就足夠了,這是一個在PHP內核中實現的C函數,它使我們能夠執行任意PHP代碼,而不必過渡到其他相關的庫中。


第一個問題可以通過使用Return-oriented programming

(https://en.wikipedia.org/wiki/Return-oriented_programming)來解決,可以在其中利用二進位文件本身或它導入的庫中已經存在的內存片段。


但是,解決第二個問題需要找到zend_eval_string函數的正確起始地址。通常,執行動態連結程序時,加載器會將進程映射到0x400000,這是x86_64上的標準加載地址。


如果可以通過某種方式獲得了Pornhub伺服器中所使用的的PHP可執行文件(例如,通過找到目標所提供的確切軟體包),則可以在本地查找所需功能的偏移量。


我們發現Pornhub使用的是php5-cgi的自定義編譯版本,因此很難確定確切的PHP版本,也很難獲得有關PHP進程內存布局的任何信息。


獲取PHP binary和必須的指針

在PHP中利用UAF通常遵循相同的規則。

一旦能夠寫入已經釋放過的內存,以後再將其作為PHP內部變量(即zval)重用,就可以生成攻擊向量,以允許從任意內存中讀取數據並觸發代碼執行。


準備進行內存數據泄露

如前所述,我們需要獲得有關Pornhub伺服器上的PHP二進位文件的更多信息。

因此,第一步是利用UAF來注入一個代表PHP字符串的zval結構體。

PHP 5.6中的zval結構體的定義如下所示:

而zvalue_value欄位被定義為聯合,因此使類型混淆變得容易。

PHP中的字符串變量是用type欄位為6的zval結構體表示的。因此,它將zvalue_value這個聯合視為包含char類型的指針和length欄位的結構體,如下圖所示:

因此,製作具有任意起點和任意長度的字符串類型的zval(即type欄位為6)會產生強大的信息泄漏,當Pornhub的setcookie函數在響應頭中輸出注入的zval時,就會觸發該信息泄漏。


獲取PHPd的image base

通常,可以從泄漏二進位文件的相關信息開始,如前所述,二進位文件的起始地址一般從0x400000開始。

不幸的是,Pornhub的伺服器使用了PIE和ASLR等保護機制,這些機制隨機化了可執行文件及其導入的共享庫的加載基址。

隨著越來越多的發行版軟體包支持位置無關代碼,這也已成為默認設置。


接下來的挑戰是:找到二進位文件的正確加載地址。


第一個困難是要以某種方式獲得一個我們可以從其泄漏的有效地址。

在此有助於了解有關PHP內存管理的一些詳細信息。


尤其是,一旦釋放了zval,PHP將使用先前釋放的塊的地址覆蓋其前八個字節。

因此,獲得第一個有效地址的技巧是創建一個整數zval,釋放該整數zval,最後使用指向該zval的懸空指針獲取其當前值。

由於php-cgi的實現中所有的worker都是由主進程使用fork系統調用產生的,因此只要不斷發送相同大小的數據,內存布局就不會在不同請求之間發生改變。

這樣的話我們就可以不斷的發送請求,並每次修改zval字符串指向的地址來泄漏不同內存地址的數據。

但是,單靠獲取已釋放塊的堆地址不足以獲取有關可執行位置的任何線索。這是由於該chunk周圍缺少有用的信息。


為了獲得有用的地址,有一種相對複雜的技術,在反序列化過程中需要多次釋放和分配PHP結構(參見ROP in PHP applications 第67頁:https://www.owasp.org/images/9/9e/Utilizing-Code-Reuse-Or-Return-Oriented-Programming-In-PHP-Application-Exploits.pdf)。

由於我們這個漏洞的特殊性質,並且為了使複雜度儘可能低,我們使用了自己的技巧。

通過使用序列化的字符串(例如「 i:0; a:0:{} i:0; a:0:{} […] i:0; a:0:{}」)作為我們payload的一部分,我們可以利用反序列化以創建許多空數組,並在終止時釋放它們。

當初始化數組時,PHP會為其zval和哈希表連續分配內存。

空數組的一個默認哈希表條目是uninitialized_bucket符號。

總而言之,我們能夠獲得類似於以下內容的內存片段:

地址0xeae040是PHP的uninitialized_bucket的符號地址,直接指向PHP的BSS段。您可以看到它在最後釋放的塊附近多次發生。

如前所述,釋放了許多空數組

因此,通過利用某些哈希表條目在堆中保持不變的情況,我們能夠泄漏這個特定符號。

最後,我們可以從uninitialized_bucket符號地址開始應用逐頁向後掃描,以找到ELF標頭:


獲取PHP binary segments

此時的情況更加複雜,因為每個請求只能泄漏1 KB數據(這是由於Pornhub的伺服器限制標頭的大小)。一個PHP二進位文件最多可能占用30 MB的大小。

假設每秒發出一個請求,則整個泄露過程將花費大約8小時20分鐘才能完成。由於我們擔心這個漏洞利用過程隨時可能被中斷,因此必須儘可能快且隱秘地進行。

所以我們要先使用一些啟發式算法來預先猜測一些可能有用的部分。不過,我們還可以解析ELF的字符串表和符號表中的結構。

還有其他一些技術(例如ret2dlresolve)可以省略整個泄漏過程,但由於需要製作更多的數據結構並且需要知道更多內存位置的信息,因此並非完全適用於此。

要獲取zend_eval_string的地址,首先必須找到偏移量為32的ELF程序頭,然後向前掃描,直到找到類型2(PT_DYNAMIC)的程序標頭條目,以獲取ELF的動態部分,這其中包含對字符串和符號表(類型5和6)的引用,可以通過使用它們的size欄位獲取任何函數的內存地址。

另外,您也可以使用哈希表(DT_HASH)更快地找到函數,但是在這種情況下,這無關緊要,因為我們可以在本地快速遍歷表。

除了zend_eval_string之外,我們還對其他符號和POST變量的位置感興趣(因為稍後它們應被用作ROP堆棧)。


獲取post數據的地址

要獲取提供的post數據的地址,您可以通過讀取以下內容來泄漏更多的指針:

遍歷該鏈看起來很複雜,其實只需要解引用一些具有正確偏移量的指針,即可快速找到指向堆中POST數據的stdin流。


準備ROP palyload

第二部分涉及控制PHP流程並獲得任意代碼執行。為此,我們首先需要討論如何修改指令指針(RIP)。


控制指令指針寄存器

我們將有效負載調整為包含偽造的對象(而不是先前使用的字符串zval),並帶有指向特製zend_object_handlers表的指針。

本質上,該表是一個函數指針數組,其結構定義可以在以下位置找到:

當創建這樣一個偽造的zend_object_handlers表時,我們可以簡單地設置add_ref。這個指針指向的函數通常用於增加對象的引用計數。


一旦我們創建的偽造的對象作為參數傳遞給setcookie函數,就會發生以下情況:

在這裡,根據「 s | sl […]」,可以看到setcookie函數將字符串作為其第一和第二個參數(|表示可選參數的開始)。


因此,它將嘗試將第二個參數傳遞的對象轉換為字符串。最後,_zval_copy_ctor將執行:

特別是,這將使用我們對象的地址作為參數來調用提供的add_ref函數(參見PHP Internals Book –複製zval以查看說明)。


相應的程序集如下所示:

RDI是_zval_copy_ctor_func函數的第一個參數,這也是我們偽造的對象zval(以上原始碼中的zvalue)的地址。


如先前在_zvalue_value typedef的定義中所見,對象包含zend_object_value類型的obj元素,其定義如下:

因此,0x8(%rdi)將指向_zend_object_value中的第二個條目,它對應於第一個zend_object_handlers條目的地址。

如前所述,該條目是我們自定義的add_ref函數,並說明了為什麼我們也可以直接控制RAX。


為了繞過前面討論的不可執行內存的問題,我們必須獲得更多的信息。

特別是,由於對堆棧的控制還不夠,我們需要收集有用的gadgets並為構造ROP鏈準備stack pivoting。


獲取ROP gadgets

現在我們可以分別設置add_ref指針或RAX來接管指令指針(instruction pointer)。

儘管這提供了一個起點,但並不能確保所有ROP gadgets都已執行,因為一旦從第一個gadget返回,CPU就會從當前堆棧中彈出下一條指令的地址。


我們對此堆棧沒有任何控制權,因此,有必要將堆棧轉移到我們的ROP鏈中。


這就是為什麼下一步是將RAX複製到RSP並繼續從那裡進行ROP的原因。

使用本地編譯的PHP版本,我們搜索了可用於stack pivoting gadget的代碼片段,並發現php_stream_bucket_split包含以下代碼:

這可以被用來修改RSP以指向我們的POST數據提供的ROP鏈,並有效地連結了所有提供的gadget calls。


根據x86_64調用約定,函數的前兩個參數是RDI和RSI,因此我們也必須找到pop %rdi和pop %rsi對應的gadgets。

這些是很常見的,因此很容易找到。但是,我們仍然不知道這些gadgets是否確實存在於Pornhub的PHP版本中。因此,我們必須手動驗證它們的存在。


驗證所需ROP gadgets在目標伺服器是否存在

infoleak向量使我們能夠快速轉儲反彙編的php_stream_bucket_split函數並檢查我們的stack pivoting gadget在遠程版本上是否可用。

幸運的是,只需要對gadgets的偏移量進行少量校正即可。

最後,我們實施了一些檢查以確認所有地址都是正確的:


製作ROP stack

最終的ROP payload可以執行如下PHP代碼:
zend_eval_string(code); exit(0);
這個payload看起來像下面的代碼片段:

因為stack pivot包含pop %r13和pop %r14,所以在其餘ROP鏈中需要使用0xdeadbeef填充來繼續設置RDI。

作為zend_eval_string函數的第一個參數,需要RDI指向要執行的代碼的內存地址。該代碼位於ROP鏈之後。

還需要在每個請求之間保持發送完全相同的數據量,以使所有計算出的偏移量保持正確。

這是通過在需要的地方設置不同的填充來實現的。


下一步是通過返回PHP解釋器最終觸發代碼執行。

實際上,諸如return2libc之類的其他技術也同樣適用,但是會產生一些其他問題,這些問題在PHP的上下文中更容易解決。


Returning into PHP

能夠執行任意PHP代碼是重要的一步,但是能夠查看其輸出同樣重要,除非有人想利用側信道接收響應。

因此,剩下的棘手部分是以某種方式在Pornhub的網站上顯示輸出結果。


Clean termination of PHP

通常,php-cgi將生成的內容轉發回Web伺服器,以便將其顯示在網站上,但是由於壞的控制流使得PHP異常終止,因此其結果將永遠不會到達HTTP伺服器。


為了解決這個問題,我們只是簡單地配置PHP使用通常用於HTTP流傳輸的直接無緩衝響應:

最終,這使我們可以直接獲取生成的PHP payload的每個輸出,而不必擔心CGI進程將數據發送到Web伺服器時通常涉及的清理例程。


這通過最小化潛在的錯誤和崩潰的數量,進一步增加了攻擊過程的隱蔽性。


總而言之,我們的payload包含一個偽造的對象,其add_ref函數指針指向我們的第一個ROP gadget。

下圖將這個概念形象化:

連同通過POST數據提供的ROP stack,我們的payload執行了以下操作:

1.創建了我們的偽造對象,該偽造對象隨後作為參數傳遞給setcookie函數。

2.這導致了對我們提供的add_ref函數的調用,即它使我們獲得了程序計數器(program counter)的控制權。

3.然後,我們的ROP鏈準備了所有已討論的寄存器/參數。

4.接下來,我們可以通過調用zend_eval_string函數來執行任意PHP代碼。

5.最後,整個攻擊過程使得程序可以正常的終止,同時還從響應主體中獲取了輸出。

一旦運行了上面的代碼,我們就可以看到Pornhub的「/etc/passwd」文件。

不僅如此,我們還能夠執行其他命令,或者直接脫離PHP執行任意系統調用。但是,此時利用PHP是更方便的。

最後,我們轉儲了有關底層系統的一些信息,並立即編寫了報告並通過Hackerone向Pornhub提交

翻譯:ChaMd5安全團隊 本文為翻譯文章(有刪改),原文連結

https://www.evonide.com/how-we-broke-php-hacked-pornhub-and-earned-20000-dollar/


今天你知道了嗎

點擊下方《了解更多》,可免費觀看更多精彩黑客教程呦~

加群,黑客技術大咖在線解答(群號評論區見)

關鍵字: