Author:p0wd3r,dawu(知道創宇404安全實驗室)
Data: 2016-12-13
更新于 12/16 : 修正了原文中的一處錯誤,感謝 @k0pwn。
2016年12月7日,國外網站exploit-db上爆出一個關于NETGEAR R7000路由器的命令注入漏洞。一時間,各路人馬開始忙碌起來。廠商忙于聲明和修復,有些人忙于利用,而我們則在復現的過程中尋找漏洞的本質。
一.漏洞簡介
1.漏洞簡介
2016年12月7日,NETGEAR R7000路由器在exploit-db上被爆出存在遠程命令執行漏洞,隨著安全研究人員的不斷深入,R8000和R6400這兩款路由器也被證實有同樣的問題。
2016年12月13日,NETGEAR官網上確認漏洞存在,對部分受影響的設備發出了beta版的固件補丁。
2016年12月14日,受影響的設備型號增加至11種。
2.漏洞影響
NETGEAR R6250 NETGEAR R6400 NETGEAR R6700 NETGEAR R6900 NETGEAR R7000 NETGEAR R7100LG NETGEAR R7300DST NETGEAR R7900 NETGEAR R8000 NETGEAR D6220 NETGEAR D6400*
二.漏洞復現與分析
1.漏洞復現
通過ZoomEye網絡空間搜素引擎我們可以尋找此次受影響的設備的ip
curl -v "https://ip:port/cgi-bin/;echo$IFS"testt" --insecure

2.漏洞分析
①靈感篇
此次漏洞分析的靈感源于小伙伴檢測中發現的一個問題,讓我們走了不少彎路,順利定位到問題所在。
當我們執行ps命令時,出現了如下的結果:
很有意思的是,我們請求的url出現在了以nobody用戶運行的進程里,這讓我們可以根據關鍵詞"sh -c"和"/tmp/cgi_result"去定位關鍵代碼的位置。
在漏洞分析的過程中,另一個小伙伴給出了一個鏈接,DD-WRT HTTPd的遠程命令執行。其中命令執行的部分如下,與本次漏洞很像。于是HTTPD就成了我們這次重點關注的對象。

從官網下載NETGEAR R7000的固件并通過如下命令解開固件。
binwalk -eM R7000-V1.0.7.2_1.1.93.chk
解開固件之后我們尋找到了相關的文件/usr/sbin/httpd,確認與cgi_result有關之后,我們對httpd進行了逆向。

②逆向篇
根據前文的介紹,我們通過搜索字符串,找到了對應的函數sub_36C34,F5看反編譯的偽碼,出現cgi_result這一字符串的地方如下:
if ( !strcmp((const char *)&v53, "POST") )
{
...
}
else if ( !strcmp((const char *)&v53, "OPTIONS") )
{
...
}
else
{
v36 = fopen("/tmp/cgi_result", "r");
if ( v36 )
{
fclose(v36);
system("rm -f /tmp/cgi_result");
if ( acosNvramConfig_match((int)&unk_F0378, (int)"2") )
puts("\r\n##########delete /tmp/cgi_result ############\r");
}
v33 = (const char *)&unk_F070F;
v34 = (char *)&v45;
}
sprintf(v34, v33, &v50);
system((const char *)&v45);
memset(&v49, 0, 0x40u);
可以看到else里的邏輯是先判斷/tmp/cgi_result這一文件是否存在,存在則刪除該文件,v33為unk_F070F的地址值,unk_F070F的值為/www/cgi-bin/%s > /tmp/cgi_result,v34為v45的地址值。繼續向下,可以看到v50替換了v33中的 %s 并賦值給了v34。v50具體是什么我們暫時不清楚。然后再使用system() 函數執行v45對應的值,也就是前面v34的值。
。據此推斷,此處應該就是命令執行的觸發點了。我們開始向上溯源。
首先,我們先看一下sub_36C34函數的幾個參數的內容
int __fastcall sub_36C34(const char *a1, int a2, const char *a3, int a4)
根據其中的代碼內容以及寫入的文件/tmp/post_data.txt可知,a1為POST數據包的body部分,a3可能為url,a4為一個整數,用于判斷是否為POST的數據包
查看xrefs graph to生成的調用圖:

我們從main函數開始看起,main函數直接調用了sub_147A0函數
sub_147A0函數中如此調用了sub_100A0函數
sub_100A0(&s1, a105, (int)&a87, dword_F217F8);
其中s1為http報文內容,a105為s1的地址值。
在sub_100A0函數中,POST數據交給sub_19600函數處理,GET數據交給sub_19B3C函數處理,還有一些其它情況交給sub_1A1C0函數處理,再交給sub_19B3C處理。我們跟了調用sub_19600函數的一些關鍵過程。
int __fastcall sub_100A0(char *a1, const char *a2, int a3, int a4)
{
char *v9; // r4@5
const char *v10; // r3@6
int v11; // r7@6
bool v12; // zf@6
...
s1 = a1;
v9 = (int)s1;
...
do
{
v10 = (unsigned __int8)*v9;
v11 = v9++;
v12 = v10 == 0;
if ( v10 )
v12 = v10 == 32;
}
while ( !v12 );
//移動到HTTP報文第一個空格的位置
...
LABEL_27:
if ( *(_BYTE *)(v11 + 1) == 47 )
++v9;
//移動到HTTP報文中/的位置
..
return (int)sub_19600((const char *)v9, v248, v4);
}
可以看到,sub_19600函數將指針移動到HTTP報文中第一個/的位置,也就是網站路徑的位置,然后通過調用LABEL_27:就可以獲取網站的目錄。這樣獲取到的內容就是最初始的內容被傳遞到了下一個函數 sub_19600。
char *__fastcall sub_19600(const char *a1, const char *a2, int a3)
{
const char *v3; // r6@1
const char *v4; // r4@1
int v5; // r5@1
char *result; // r0@1
v3 = a2;
v4 = a1;
v5 = a3;
result = strstr(a1, "cgi-bin");
if ( result )
{
if ( acosNvramConfig_match((int)"cgi_debug_msg", (int)"1") )
printf("\r\n##########%s(%d)url=%s\r\n", "handle_options", 1293, v4);
result = (char *)sub_36C34(v3, v5, v4, 2);
}
return result;
}
sub_19600函數沒有做任何處理,就直接將獲取到的路徑傳遞到了sub_36C34。
int __fastcall sub_36C34(const char *a1, int a2, const char *a3, int a4)
{
v6 = a3;
v12 = strstr(v6, "cgi-bin");
if(v12)
{
...
memset(&v50, 0, 0x40u);//給V50分配了64字節的空間,故我們可執行命令的最大長度為64字節
...
}
else
{
if ( v24 )
{
if ( v22 )
v25 = 0;
else
v25 = v23 & 1;
if ( v25 )
strcpy((char *)&v50, v20);
}
else
{
strncpy((char *)&v50, v20, v22 - 1 - v21);
}
}
...
...
...
if ( !strcmp((const char *)&v53, "POST") )
{
...
}
else if ( !strcmp((const char *)&v53, "OPTIONS") )
{
...
}
else
{
v36 = fopen("/tmp/cgi_result", "r");
if ( v36 )
{
fclose(v36);
system("rm -f /tmp/cgi_result");
if ( acosNvramConfig_match((int)&unk_F0378, (int)"2") )
puts("\r\n##########delete /tmp/cgi_result ############\r");
}
v33 = (const char *)&unk_F070F;
v34 = (char *)&v45;
}
sprintf(v34, v33, &v50);
system((const char *)&v45);
memset(&v49, 0, 0x40u);
}
在sub_36C34函數中,會檢測url中是否含有cgi-bin,如果含有,則進行一系列分割操作,并將cgi-bin后面的值賦給v50,而參數v50則正如我們之前分析的那樣,替換了v33中的 %s 之后賦值給v34并被 system() 函數執行,造成了命令執行漏洞。
之后我們繼續跟了sub_19B3C和sub_1A1C0這兩個函數,發現最終也跟sub_19600函數殊途同歸。不過是因為HTTP請求的不同(POST和OPTIONS)而導致不同的函數去處理罷了。
③固件對比篇
2016年11月13日,NETGEAR在其官網發布了新的beta固件,我們對其進行了更進。
按照上文同樣的方法,我們對httpd進行了逆向,xrefs圖如下:
整體函數沒有太大變化,讓我們來看一下具體細節上的變化。
一路跟下來,在sub_14958,sub_100A0和sub_197B8這些函數中都沒有看到對url進行處理,在sub_36EB4中我們發現官方對其進行了過濾。
int __fastcall sub_36EB4(const char *a1, int a2, const char *a3, int a4)
{
const char *v6; // r4@1
...
v6 = a3;
if ( !strchr(a3, 59) && !strchr(v6, 96) && !strchr(v6, 36) && !strstr(v6, "..") )
//ascii對照表:59=>; 96=>` 36=>$
{
...
}
}
我們可以看到,官方的beta固件過濾了; \ $和..,但是我們依舊可能可以繞過這些過濾執行命令,例如||(未測試)。
至于官方后續的更新,我們會繼續更進。 如有錯誤,歡迎指正:)
三.漏洞影響
下圖為ZoomEye網絡空間搜素引擎上最早曝光的受影響設備R7000,R8000和R6400的全球分布情況。

事實上,還有很多內網的設備我們無法探測,對于這些內網設備,通過csrf攻擊仍然可以威脅到內網的安全。這里提供一個以往的案例供大家參考: http://www.2cto.com/article/201311/254364.html 。由于本次漏洞可以執行任意命令,故威脅遠比案例中修改dns要大,希望可以引起大家的重視。
四.修復建議
目前官方僅推出了beta版的補丁,可以根據官網的提示刷新固件
由于新版本beta固件可能還存在一定的安全問題,我們仍然建議關閉路由器遠程管理頁面。
對比之前受影響的設備,如果不能進入管理界面,也可以通過下列url關閉。
http(s)://ip:port/cgi-bin/;killall$IFS’httpd’
五.參考鏈接
1.https://www.seebug.org/vuldb/ssvid-92571 2.https://www.exploit-db.com/exploits/40889/ 3.http://kb.netgear.com/000036386/CVE-2016-582384 4.http://www.kb.cert.org/vuls/id/582384 5.https://github.com/rapid7/metasploit-framework/issues/7698 6.http://www.freebuf.com/news/122596.html
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/145/