作者:啟明星辰ADLab
背景:
國慶前,一位國外安全研究員在 The Fuzzing Project 網站上寫了一篇 blog,文章描述了他發現的一個 Apache 信息泄漏漏洞(CVE-2017-9798),并給該漏洞起名“Optionsbleed”,意為發生在Options請求中的出血漏洞。盡管使用了“bleed”這個名字,但是作者也在文章中坦言,該漏洞的影響確實無法與心臟出血“Heartbleed”(CVE-2014-0160)漏洞媲美。首先,這個漏洞影響的目標并不像心臟出血那么多,其次該漏洞只能泄漏很少(甚至什么都泄漏不出來)內存。
漏洞作者僅僅給出了 ASAN 的分析截圖和漏洞復現方法,并未紕漏更多細節,本文啟明星辰ADLab安全研究員將對此漏洞進行詳細深入地分析。
漏洞復現:
根據 blog 的描寫,想要觸發漏洞,需要在服務器目錄下添加一個.htaccess文件(這是一個配置文件),并且需要在該文件中添加一個沒有被全局注冊過的 Limit 指令。為此,ADLab研究員下載編譯了存在漏洞的 Apache 版本2.4.27,修改了漏洞作者提供的poc并測試發包,在配置沒有問題的服務器上結果是這樣的。

接著在Apache服務器子目錄下創建.htaccess文件,并在文件中寫入如下內容:
<Limit abcxyz>
</Limit>
證明漏洞存在(如下圖“OPTIONS”后面多出來的“,”)。

漏洞分析:
事實上,通過漏洞作者提供的 poc 向 Apache 發消息后,Apache服務器一共處理了兩次連接請求(之所以產生兩次連接,涉及到 uri 重定向等一系列問題,細節和本漏洞無關所以不再贅述,感興趣的可以參考mod_dir.c及相關文件了解詳情)。 每次處理連接請求時 Apache 都會為當前的連接分配一個 request 結構體,該結構體比較復雜,包括了各種連接相關的信息,這里我們只關心它的第一個變量 pool,即 request 的內存池。

Apache 服務器自行維護一套內存管理機制,這套內存管理機制最大的特點就是簡單,它并不是按照大小將空閑的內存塊連接到不同的鏈表上以期提高內存管理效率、減少內存碎片并節約內存,甚至不會頻繁地釋放無用內存。相反,Apache 會把下一塊可用內存指針指向空閑內存的起始地址,不管需要分配多大的內存,都從空閑內存的起始地址開始分配,并將這個空閑地址的指針后移。相應的,當分配的內存不再被需要的時候,也不會主動的釋放這塊內存方便重復利用,而是選擇在網絡連接的收尾階段將整塊request_rec的 pool 釋放掉。
Apache 這么做可能主要基于服務器對每個網絡連接處理的時間并不長,每個網絡連接需要的內存池并不算太大這兩個原因。所以,采用這種方式來管理內存不僅不會造成對內存的浪費反而提高了服務器的響應效率。
第一次連接:
當服務器處理第一次連接的時候,Apache 為 request 分配的內存池地址為 0xaa102488。

Apache 根據請求的地址遍歷其目錄,當遍歷到被請求目錄時發現該目錄下有.htaccess文件,就會打開該文件讀取并處理文件內容,當讀到<Limit abcxyz>這一行時,服務器會從request地址池里分配一塊內存用于存放 abcxyz 字符串。

然后,流程進入ap_method_register函數。

這個函數是非常重要的函數,我們來看一下其實現:

該函數中的methods_registry是一個apr_hash_t類型的結構體指針,這是一個用于描述服務器中注冊方法的結構體,用 hash 表的方式來保存和查找注冊的方法。實現如下:

其中,array是hash表數組,數組中的每一項都指向一個由若干個 hash 節點串聯起來的單向鏈表。每個節點的數據結構如下:

從代碼上看,服務器首先會調用apr_hash_get來查找是否該方法(即 Limit 指令指定的方法)已經被注冊,之前我們已經說過觸發漏洞需要添加一個沒有被全局注冊過的 Limit 指令(本例中是 abcxyz),所以該查找一定會失敗。而register_one_method函數一定會被執行,這個函數主要是對apr_hash_set的封裝,所以重點看一下apr_hash_set函數以及其子函數find_entry。
apr_hash_set函數功能是對 hash 表進行增刪改操作,而函數的最后一個參數 val 就是用來確定到底要對 hash 表進行哪種操作的。如果 val 為0就對找到的hash節點進行刪除操作,不為0就對 hash 表進行插入或者重新賦值操作,具體來說就是當這個 hash 節點不存在的時候就插入一個新的節點,否則對 val 重新賦值。find_entry的解釋請參見我們在代碼中添加的注釋。

在 hash 插入之前內存如下,我們看到在 array[3]中只有兩個 hash 節點。

當節點被插入后內存如下,我們看到 hash 表中多了一個節點,這個節點就是我們在.htaccess中定義的 Limit 指令 abcxyz。

當 Apache 將回應包通過 socket 發出后,request 地址池將會被釋放(methods_registry中 abcxyz 的 key 和 val 的地址也在這塊內存池中)。但是,通過對圖中內存的觀察,我們發現methods_registry中對 abcxyz 的引用還在。(Apache 的內存管理在釋放內存之后并不會將內存中的數據覆蓋,所以這時候我們在調試器里還能看到原有的數據)。

至此,第一次連接的主要流程已經走完,留下了一個對已經釋放的地址有引用的 hash 節點。
第二次連接:
第二次連接首先分配內存池。

繼續往下進行,當服務器對配置文件中的新配置項進行添加的時候會執行如下指令,從 request 內存池里分配一塊內存,該內存起始地址是 0xaa107718 大小為152字節,并通過 memcpy 函數對這塊內存進行內存拷貝工作,拷貝之后 0xaa107758 和 0xaa107760(分別保存著釋放前 hash 節點的 key 和 val,見第一次連接中 request 地址池釋放圖片中的藍色方框)兩處的內容都被覆蓋成了0。

當服務器再次進行到注冊的時候,由于 hash 節點 0x811b5d0 處的 key 和 val 都已經被拷貝成了新的數據,所以注冊函數認為 abcxyz 的節點不存在,于是重新分配了一個節點。原來的節點就成了一個指向了空字符串且 val 為 0 的節點。

最后,當服務器開始對 Options 進行響應的時候,會進入一個叫做 make_allow 的函數,其作用就是生成 Options 返回的支持方法的字符串,這個函數中有一個很重要的變量 mask,它是一個選擇子,用來判斷 hash 表上哪些節點是符合要求的,在我們的調試器里這個值是37。

make_allow具體實現如下,函數中的AP_METHOD_BIT是 int64 型的1,當 mask 值為37(二進制100101)時,要滿足 mask 的篩選條件 hash 節點的 val 值需要為0、2或5。0x811b5d0 這個節點(已經釋放的abcxyz)本來是不符合要求的,但是由于其 val 已經被寫成0了,所以在這里也符合了篩選條件,這就是我們之前看到 allow 中多了一個逗號的原因。其實真正的情況是 allow 里多出了一個字符串,不過這個字符串為空字符串,所以只是多了一個分割用的逗號。

補丁分析:
在補丁中,我們看到服務器在注冊methods_registry前進行了驗證,不讓.htaccess全局注冊新方法,但并未真正地修補漏洞的 root cause,而是通過一個判斷來阻止該漏洞執行到有問題的代碼流程上。

結論:
這是一個由 UAF 導致的信息泄漏漏洞,正如漏洞作者所說這個漏洞的影響確實遠小于心臟出血,原因如下:
第一,使用.htaccess文件進行服務器配置本身并不被Apache官方推薦,因為這會極大的影響服務器的響應效率。理論上,只有對特定目錄設置權限之類的特殊情況,.htaccess文件才應該被使用,再加上想要觸發漏洞還要滿足.htaccess文件中需定義一個未被全局注冊過的Limit指令這一條件,使得符合情況的機器大幅減少。
第二,漏洞利用程序不一定能夠穩定運行,只有當hash節點的val被覆蓋成了0、2或5的情況下,字符串才能被當作是合法的方法加入到allow數組中。
第三,也是最重要的一點,這個漏洞泄漏的內存數據并不大。
參考
Optionsbleed - HTTP OPTIONS method can leak Apache's server memory
啟明星辰積極防御實驗室(ADLab)
ADLab成立于1999年,是中國安全行業最早成立的攻防技術研究實驗室之一,微軟MAPP計劃核心成員。截止目前,ADLab通過CVE發布Windows、Linux、Unix等操作系統安全或軟件漏洞近400個,持續保持國際網絡安全領域一流水準。實驗室研究方向涵蓋操作系統與應用系統安全研究、移動智能終端安全研究、物聯網智能設備安全研究、Web安全研究、工控系統安全研究、云安全研究。研究成果應用于產品核心技術研究、國家重點科技項目攻關、專業安全服務等。

本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/437/
暫無評論