本文來源:長亭技術專欄
作者:楊坤
寫在前面
很多技術宅們都喜歡折騰自己的路由器,例如在上面搭建NAS、配置遠程下載和使用代理上網,這些技術和相關軟件能幫助大家在硬盤里搜集大量的娛樂、教育資源。
不過大家在網上下載和使用第三方軟件時要小心啦,要多關注軟件的安全更新。長亭安全研究實驗室在2016年5月通過GeekPwn向華為PSIRT報告了迅雷固件Xware的多個漏洞,這些漏洞不僅存在于華為榮耀路由默認支持的遠程下載功能中,也會影響使用Xware的其他路由器或Linux服務器。在收到漏洞報告后,華為官方迅速給出了修復,華為榮耀路由早已不受影響。不過,迅雷官方在2016年2月就已經宣布停止維護該固件,github上基于Xware的一些開源項目也因此棄坑(例如Xinkai/XwareDesktop和PointTeam/PointDownload)。目前散落在網上的軟件版本很可能是未得到修復的,在這里建議大家盡量避免使用已過期且無官方支持的Xware軟件。
在本文中,筆者會給大家分享一下漏洞的細節和利用思路。閱讀本文不需要任何安全研究方面的經驗,只需要一點點棧溢出的基本知識。還不知道棧溢出的讀者請移步長亭技術專欄的《手把手教你棧溢出從入門到放棄》上下兩篇文章,如果您讀完后真的準備放棄,那不妨試試本文,或許本文能給你一次重新選擇的機會。
說點歷史
棧溢出攻擊的相關概念最早要追述到1972年美國空軍發表的一份研究報告《Computer Security Technology Planning Study》。在這份報告中,通過溢出緩沖區來注入代碼這一想法首次被提了出來。大家來感受一下最早的描述原文:

棧溢出的概念雖然早就提出,但直到1988年才出現了首次真實的攻擊,Morris蠕蟲病毒利用了Unix操作系統中fingerd程序的gets()函數導致的棧溢出來實現遠程代碼執行。
1996年,Elias Levy (a.k.a Aleph One)在大名鼎鼎的Phrack雜志上發表了文章《Smashing the Stack for Fun and Profit》,從此棧溢出漏洞的利用技術被廣泛知曉。
也許有讀者會疑惑,棧溢出這樣的低級錯誤現在還存在嗎?本文要介紹的幾個2016年發現的漏洞中,最為關鍵的一個漏洞就是棧溢出。從最早提出這一概念的1972年到現在已經有四十多年的歷史,經歷了將近半個世紀,程序員們依然會在這一看似簡單的問題中跌倒。這一情況并非個案,在路由器這類嵌入式設備中依然普遍存在,長亭安全研究實驗室在2016年GeekPwn中攻破了10款路由器,利用的漏洞中大多數還是棧溢出。而即便是經歷了多年發展PC端操作系統,也同樣存在棧溢出:在Pwn2Own 2017上,來自美國的Richard Zhu找到了Mac操作系統中的棧溢出漏洞。
來到2017年的今天,面對棧溢出,我們依然不能說放棄。
路由器被搞,有哪些危害?
路由器作為家庭上網的入口,其安全重要性不言而喻。家里所有智能設備、電腦都需要通過連接路由器上網,一旦路由器淪陷,攻擊者就可以看到所有明文上傳和下載的流量。還記得2016年的央視315晚會嗎?節目現場就演示了在WiFi端截獲手機App的上網流量,其中包含姓名、電話、生日、家庭地址、訂單等隱私信息。更嚴重的是,攻擊者還可能通過篡改流量進一步入侵連接這臺路由器的設備,例如在你下載某一個Windows安裝包或者安卓應用APK文件時,偷偷將其替換成植入后門的版本。
不開防火墻,后果很嚴重
路由器有哪些可能被入侵的途徑呢?一般來說,家用路由器用于組建家庭局域網(LAN)。對于公網連接的部分(WAN口),路由器往往都配置了防火墻,禁止了公網對路由器自身服務的訪問,這樣即便路由器存在漏洞,也不至于暴露在“大庭廣眾”之下。長亭安全研究實驗室就在GeekPwn上披露過一款在此問題上出現疏忽的路由器,這就導致攻擊者可以直接在公網上利用路由器的漏洞來入侵成千上萬個目標。
入侵路由兩步走
對于大多數開啟防火墻的路由器來說,入侵的第一步就是接入路由器局域網絡(LAN),這一步有好多種方法可以嘗試:Wifi萬能鑰匙、破解WEP加密、破解WPS PIN碼、使用字典爆破Wifi密碼等等。而對于公共場合的路由器來說,這一步就不是問題了,Wifi密碼是公開的,任何人都可以直接接入。
接入路由器網絡后,第二步就是利用路由器自身的缺陷來取得路由器的完全控制權,本文介紹的案例漏洞就是用在這一步。路由器的漏洞主要存在于自身開啟的軟件服務當中,例如幾乎每個路由器都會有一個開啟在80端口的Web管理界面,還有其他常見服務例如用于分配IP地址的服務DHCP、即插即用服務UPnP等,這些服務會監聽在某個TCP/UDP端口,接入路由器網絡的攻擊者可以通過向這些端口發送特定數據包來實施各種類型的攻擊,例如權限繞過、命令注入、內存破壞等。
說完了攻擊的場景,讓我們回到本文的目標上來。如果路由器自帶或者手動配置了迅雷遠程下載功能,Xware軟件會監聽一些端口,其中包含一個處理HTTP協議的端口,在某款路由器上為9000,本文介紹的漏洞就是跟這個服務有關。大家可以在這里下載官方停止維護之前放出的最后一個版本。
一串漏洞來襲
官方提供的Xware軟件以及路由器固件中自帶的Xware軟件都只有編譯好的二進制文件,通過逆向分析,我們一共發現了三個問題,每個問題單獨來看都無法造成嚴重影響,但是三個漏洞經過組合利用便可以達到遠程任意代碼執行的效果。
漏洞一:你真的會用snprintf嗎:信息泄漏
學過C語言的同學都知道snprintf函數的用法,這是最基本的字符串處理函數之一。基本形式如下:
int snprintf(char *str, size_t size, const char *format, ...);
眾所周知,我們可以通過指定snprintf的第二個參數size來防止緩沖區溢出的發生,然而你是否真正理解snprintf返回值的含義?大家先來看看下面幾行代碼,猜猜代碼的輸出是什么:
int main() {
char buf[8];
int n = snprintf(buf, 8, "%s", "1234567890");
printf("buf: %s\n", buf);
printf("n: %d\n", n);
}
相信第一個buf的輸出難不倒大多數人,而返回值n的輸出一定會讓一部分人吃驚:
buf: 1234567
n: 10
難道snprintf函數返回的不是打印字符的個數嗎?讓我們來查一查文檔,下面是摘錄自man中的一段解釋:
The functions snprintf() and vsnprintf() do not write more than size bytes (including the terminating null byte (’\0’)). If the output was truncated due to this limit, then the return value is the number of characters which would have been written to the final string if enough space had been available. Thus, a return value of size or more means that the output was truncated.
確實,snprintf返回的是打印字符的個數,但是這個數字是在假設沒有第二個參數size限制的情況下統計的。這一細節同樣困惑了Xware開發人員,我們來看一看存在漏洞的代碼(下圖為IDA的反編譯結果):

在上面這段代碼中,snprintf的返回值v19被用作HTTP響應包的實際長度傳入em_comm_send函數。HTTP響應包的實際長度實際上會受到snprintf的第二個參數0x100的限制,但返回的長度v19實際上沒有這個限制,因此http響應在有些情況下會輸出超過0x100的字符,buf緩沖區后面的數據會被返回。buf緩沖區實際分配在堆上,因此這個漏洞能夠用來泄露堆上的數據。
觸發漏洞時返回的HTTP響應數據如下所示:

這個漏洞的CVE編號是CVE-2016-5367。
漏洞二:INI配置注入漏洞
INI是一種常見的初始化配置文件格式,INI就是Initialization的前三個字母。INI文件的格式非常簡單,由多個節(Section)組成,每個節由一行行的鍵值對組成,如下所示:
[section1]
key1=value1
key2=value2
[section2]
key3=value3
Xware的HTTP服務存在一個登錄接口login,該接口會解析HTTP請求中cookie里的一些參數,并將值保存入ini配置文件中。例如cookie = "isvip=0; jumpkey=A; usernick=B; userid=1"時,ini配置文件中會寫入用戶相關的參數,樣例配置文件如下:
[license]
server_addr=X.X.X.X
[huiyuan]
userid=1
username=B
vip_level=0
jumpkey=A
細心的話會發現在ini配置文件中,不同的鍵值對都是通過換行來區分。如果鍵或值中存在換行符怎么辦?假如我們嘗試給出這樣的cookie = “isvip=0; jumpkey=A\n\n[license]\nserver_addr=1.3.3.7; usernick=B; userid=1”,那么寫入配置文件的就是:
[license]
server_addr=X.X.X.X
[huiyuan]
userid=1
username=B
vip_level=0
jumpkey=A
[license]
server_addr=1.3.3.7
這樣我們就可以在ini文件后面的位置插入一個新的配置,來修改文件前面的默認配置,此例中我們修改的是server_addr的值。
本身通過cookie來設置的配置項只能是huiyuan這個節中的指定鍵,通過在值中插入換行符,我們就實現了任意配置選項的注入。
這個漏洞的CVE編號是CVE-2016-5366。
漏洞三:發生在2016年的棧溢出
利用上述漏洞,在配置文件中把默認的license server改掉能有什么用呢?
幸運的是,Xware的這個HTTP服務器還暴露了一個接口可以重啟Xware軟件,攻擊者可以隨時調用它,來讓程序在重啟時解析被我們注入過的INI配置文件。所以讓我們看看程序在初始化過程中對配置文件中的license server做了什么。
通過逆向分析,我們找到了解析license server的相關代碼:

解析server地址和端口的代碼在parse_server_addr函數中:

此處代碼明顯存在溢出,首先memcpy函數在使用時指定的拷貝長度只與源字符串有關,其次在另個分支中直接使用了危險函數strcpy。兩處拷貝的目標緩沖區v4,即傳入parse_server_addr的第二個參數,實際是在上層函數中棧上的局部buffer,因此這里的溢出是典型的棧溢出。
這個漏洞的CVE編號是CVE-2016-5365。
漏洞利用:“組合拳”
上面三個漏洞每個漏洞單獨看都無法造成嚴重影響,即便是棧溢出漏洞也是發生在解析初始化INI配置文件的過程中,一般人都會認為配置文件中的選項都是寫死的,例如這里的license server地址,就算用strcpy也不必擔心。然而如果我們把這幾個小問題組合在一起使用時,會出現怎樣的威力呢?
首先我們可以通過堆內存的泄露找到libc庫加載的地址,因為通常linux采用的是dlmalloc/ptmalloc,堆上空閑的塊中會包含指向libc全局變量的指針(具體參考堆的實現,這里不作展開)。目前大多MIPS/ARM架構的路由器都沒有開啟地址隨機化保護(ASLR),泄露的這個地址往往是不變的。
接下來我們可以利用INI配置注入漏洞,往INI配置文件中注入超長的license server地址,并在其中植入ROP payload。由于我們已經知道libc的地址,我們就能夠使用libc中的gadget來組ROP。
最終,只要我們調用重啟Xware的接口,Xware就會重新解析INI配置文件,并在這個過程中觸發棧溢出,從而執行我們的ROP代碼。
我們用下面的流程圖來總結這三個漏洞的組合利用過程:

后記
本文以一個真實案例給大家介紹了棧溢出漏洞造成的危害。根據長亭安全研究實驗室的研究經驗,像這樣的棧溢出的案例在智能設備上還有很多很多。目前市面上的智能設備種類和品牌可謂百花齊放,這些產品在安全性的設計和實現上參差不齊。如果單從安全性考量,筆者建議讀者在選購時選擇大廠商自主研發的產品,因為大品牌廠商不僅在研發經驗上有更多的積累,而且在對待安全問題的態度上也是積極正面的。也建議大家在使用智能設備時,多關注官方的動態和安全補丁的發布,及時更新固件。
參考資料
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/269/
暫無評論