本文作者:cq674350529(信安之路 2019 年度優秀作者)
CVE-2019-13954是MikroTik RouterOS中存在的一個memory exhaustion漏洞。認證的用戶通過構造並發送一個特殊的POST請求,服務程序在處理POST請求時會陷入"死"循環,造成memory exhaustion,導致對應的服務程序崩潰或者系統重啟。
該漏洞與CVE-2018-1157類似,是由於對漏洞CVE-2018-1157的修復不完善造成。下面通過搭建MikroTik RouterOS仿真環境,結合漏洞CVE-2018-1157的PoC腳本及補丁,對漏洞CVE-2019-13954進行分析。
CVE-2018-1157漏洞分析MikroTik RouterOS環境的搭建、root shell的獲取及相關資料可參考文章《CVE-2018-1158 MikroTik RouterOS 漏洞分析之發現 CVE-2019-13955 》:
https://cq674350529.github.io/2019/08/15/CVE-2018-1158-MikroTik-RouterOS%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B9%8B%E5%8F%91%E7%8E%B0CVE-2019-13955/
根據Tenable的漏洞公告:
https://www.tenable.com/security/research/tra-2018-21
漏洞CVE-2018-1157在6.40.9、6.42.7及6.43等版本中修復。為了便於對漏洞CVE-2018-1157進行分析,選取的相關鏡像版本如下。
6.40.5,x86架構,用於進行漏洞分析
6.42.11,x86架構,用於進行補丁分析
為了便於分析,臨時關閉了系統的ASLR機制。
與該漏洞相關的程序為www,在設備上利用gdbserver附加到該進程進行遠程調試,然後運行對應的PoC腳本,發現系統直接重啟,在本地gdb中捕獲不到任何異常信息。根據漏洞公告中提到的"/jsproxy/upload",在函數JSProxyServlet::doUpload()內設置斷點,進行單步跟蹤調試,發現會一直執行如下的代碼片段。
int __cdecl JSProxyServlet::doUpload(int a1, int a2, Headers *a3, Headers *a4){ while ( 1 ) { sub_77464E9F(v27, (char *)s1); if ( !LOBYTE(s1[0]) ) break; string::string((string *)&v36, (const char *)s1); v11 = Headers::parseHeaderLine((Headers *)&v37, (const string *)&v36); string::freeptr((string *)&v36); if ( !v11 ) { string::string((string *)&v36, ""); Response::sendError(a4, 400, (const string *)&v36); string::freeptr((string *)&v36); LABEL_56: tree_base::clear(v13, v12, &v37, map_node_destr<string,HeaderField>); goto LABEL_57; } } }其中,函數sub_77464E9F()用於讀取POST請求數據並將其保存在s1指向的內存地址空間,其偽代碼如下。
char *__usercall sub_77464E9F@<eax>(istream *a1@<eax>, char *a2@<edx>){ v2 = a2; istream::getline(a1, a2, 0x100u, 10); result = 0; v4 = strlen(v2) + 1; if ( v4 != 1 ) { result = &v2[v4 - 2]; if ( *result == 13 ) *result = 0; } return result;}可以看到,當滿足以下任一條件時會跳出while循環。
查看對應的PoC腳本,其對應的部分 POST 請求數據為Content-Disposition: form-data; name="file"; filename="<filename>"\r\n。
std::string filename;for (int i = 0; i < 0x200; i++){ filename.push_back('A');}
if (jsSession.uploadFile(filename, "lol.")){ std::cout << "success!" << std::endl;}當filename參數的值過長時,調用istream::getline()讀取的內容一直為Content-Disposition:form-data; name="file"; filename="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA..."(長度超過0x100)。由於上面的2個條件都不滿足,造成while循環無法退出,一直執行最終導致"memory exhaustion"。
CVE-2018-1157補丁分析版本6.42.11中對CVE-2018-1157進行了修復,根據前面的分析,定位到JSProxyServlet::doUpload()中對應的代碼片段,如下。可以看到,在補丁中增加了對讀取的 POST 請求數據長度的判斷:當長度超過0x100(包括最後的'\x00')時,會跳出 while 循環。
由於兩次jsproxy.p的加載基址不一樣,所以部分函數的名稱可能不一致。
int __cdecl JSProxyServlet::doUpload(int a1, int a2, Headers *a3, Headers *a4){ while ( 1 ) { sub_774C31F7(v14, s1); if ( !LOBYTE(s1[0]) ) break; v15 = -1; v16 = (char *)s1; do { if ( !v15 ) break; v17 = *v16++ == 0; --v15; } while ( !v17 ); if ( v15 != 0xFFFFFEFF ) { v37 = 0; string::string((string *)&v46, (const char *)s1); v18 = Headers::parseHeaderLine((Headers *)&v47, (const string *)&v46); string::freeptr((string *)&v46); if ( v18 ) continue; } string::string((string *)&v46, ""); Response::sendError(a4, 400, (const string *)&v46); string::freeptr((string *)&v46); LABEL_60: tree_base::clear(v20, v19, &v47, map_node_destr<string,HeaderField>); goto LABEL_61; } }CVE-2019-13954發現
通過對漏洞CVE-2018-1157分析可知,調用istream::getline(a1, a2, 0x100u, '\n')讀取數據時,如果請求數據過長(在遇到分隔符'\n'前0x100個字符已被寫入a2中),那麼每次a2中的數據內容都是一樣的。而在對應的補丁中,增加了對讀取數據長度的判斷。
注意到,在調用istream::getline(a1, a2, 0x100u, '\n')讀取數據時,分隔符為'\n'。也就是說,即使filename參數的值中包含'\x00',讀取時也不會造成截斷,但是會影響後面的長度計算。因此,只需要在filename參數後面追加大量的'\x00',即可繞過補丁,再次觸發該漏洞。
在原有PoC的基礎上進行簡單修改,在版本為6.42.11的設備上進行驗證,發現系統直接重啟了。
std::string filename;for (int i = 0; i < 0x50; i++){filename.push_back('A');}
for (int i = 0; i < 0x100; i++) {filename.push_back('\x00');}
if (jsSession.uploadFile(filename, "lol.")){std::cout << "success!" << std::endl;}通過代碼靜態分析,該漏洞在"Long-term"版本6.43.16上仍然存在。
6.43.16為發現該問題時 "Long-term" 系列的最新版本。該漏洞(CVE-2019-13954)目前已被修復,建議及時升級到最新版本。
小結由於對漏洞CVE-2018-1157的修復不完善,通過在filename參數後面追加大量的'\x00',可繞過對應的補丁,再次觸發該漏洞(CVE-2019-13954)。
相關連結Mikrotik RouterOS Multiple Authenticated Vulnerabilities:
https://www.tenable.com/security/research/tra-2018-21
Two vulnerabilities found in MikroTik's RouterOS:
https://seclists.org/fulldisclosure/2019/Jul/20
Mikrotik RouterOS Changelogs:
https://mikrotik.com/download/changelogs/long-term-release-tree