0x01 前言
前两天NTP刚搞完事情,NetGear路由器(网件路由器)又来搞事了T.T。目前CERT在上周五已发布公告,“如果用户使用涉及到的路由器,建议停止使用,直到官方发布补丁修复”。此漏洞是由Acew0rm发现的,之后报给NetGear,今天,他又在Twitter上上传了关于这个漏洞利用的视频。
同时也发布了一个Exploit地址,有两个,一个是嵌入html的利用,另一个是比较直接的利用,当然,需要获取路由器的IP地址。
https://github.com/Acew0rm/Exploits/blob/master/Netgear_R7000.html
https://www.exploit-db.com/exploits/40889/
但实际上,这个漏洞影响的路由版本,远不止现在曝光的这么少,目前曝光的是R6400,R7000版本,后来CERT又曝光了R8000版本。我看了此漏洞公开后,下面有很多在使用NetGear路由的老外在聊天,其中总结了一下受漏洞影响版本的路由。
1
2
3
4
5
6
7
8
9
|
l R6400 (AC1750): confirmed l R7000 Nighthawk (AC1900, AC2300): confirmed (by myself) l R7500 Nighthawk X4 (AC2350): confirmed (by [2]) l R7800 Nighthawk X4S(AC2600): confirmed (by [2]) l R8000 Nighthawk (AC3200): confirmed l R8500 Nighthawk X8 (AC5300): confirmed (by [2]) l R9000 Nighthawk X10 (AD7200): confirmed (by [2]) l R6250 l R6700 |
几乎所有R系列的路由都受此漏洞影响,当然有一些R系列路由虽然受影响,但因为固件的不同,部分固件是不受此漏洞影响的。
目前此漏洞未提供补丁,官方在Twitter上的回复是正在抓紧时间修复此漏洞,因此应该还有不少设备受漏洞影响。
开始分析前,感谢Spongebobb在微博上和我的讨论,让我从莫名其妙的脑洞中跳转出来,23333。
0x02 检测方式
浏览器访问路由器地址:
1
|
http: //[router-address]/cgi-bin/;uname$IFS-a |
如果返回的页面是错误或者非空的,那么该路由器可能存在这个漏洞。
0x03 为了分析我掉到了好几个坑里
在昨天看到这个漏洞曝光后,我下载了对应版本的固件【_R7000-V1.0.7.2_1.1.93.chk】,分析的过程中当然碰到了不少坑,这里稍微总结一下。
关于这个漏洞,主要问题发生在/usr/sbin/httpd里,但在/www/cgi-bin/下也有一个可执行文件genie.cgi,其中也履行了CGI程序的部分功能,刚开始我比较坚定的认为在genie.cgi中,也找到了比较有趣的调用位置。
1
2
3
4
5
6
7
8
9
10
11
12
|
v6 = getenv( "QUERY_STRING" ); ptr = (void *)sub_A304(dword_1385C); if ( ptr ) { v0 = sub_9560((int)v6); if ( v0 != -1 ) { sub_9C78(v0); v4 = 0; sub_ABAC(0xB348, &v4, &v3); } } |
这里调用getenv获取了QUERY_STRING环境变量,这个变量就是通过GET方法接收到URL中参数的时候,会获取参数,并且给QUERY_STRING赋值,这个setenv赋值过程是在httpd中完成的,genie.cgi只负责getenv。而随后这里调用了一个函数sub_ABAC,跟入这个函数,我发现了在这个程序中唯一一次会调用到系统函数的位置。
1
2
3
4
5
6
7
8
9
|
.text:0000ABAC STMFD SP!, {R11,LR} .text:0000ABB0 ADD R11, SP, #4 .text:0000ABB4 SUB SP, SP, #0x420 .text:0000ABB8 STR R0, [R11, #command] .text:0000ABBC STR R1, [R11, #var_414] .text:0000ABC0 STR R2, [R11, #var_418] .text:0000ABC4 LDR R0, [R11, #command] ; command .text:0000ABC8 MOV R1, #aR_0 ; modes .text:0000ABD0 BL popen |
popen可以执行系统函数,正是符合我们exp中的条件,但是却失落的发现,这里传递的值是sub_ABAC函数第一个参数,也就是0xB348,这是一个常量。
1
|
.rodata: 0000 B 348 aInternetSetCon DCB "internet set connection genieremote 1" , 0 |
刚开始我脑洞有点开大了,想到的是类似于php的变量覆盖,会不会是URL传入的值,由于某些原因会覆盖到这个常量,后来还是否决了这个过程,一筹莫展的时候我想到了对比一下没有漏洞的版本(后来事实证明,我分析所谓没有漏洞的版本,也是有这个漏洞的),对比的时候发现R7000以后的路由版本采取https,在看配置文件的时候无意中发现了R7000中的/usr/sbin/httpd。
按照同样的思路,我找到了httpd中有两处函数调用可能调用到了系统函数,一处是popen,另一处是system。
1
2
3
4
5
6
7
8
9
10
|
.plt:0000E6BC ; FILE *popen(const char * command , const char *modes) .plt:0000E6BC popen ; CODE XREF: sub_73F40+3D4_x0019_p .plt:0000E6BC ; sub_95B44+1BC_x0019_p .plt:0000E6BC ADRL R12, 0x1086C4 .plt:0000E6C4 LDR PC, [R12, #(popen_ptr - 0x1086C4)]! ; __imp_popen .plt:0000D69C ; int system(const char * command ) .plt:0000D69C system ; CODE XREF: sub_147A0+D2C_x0019_p .plt:0000D69C ; sub_147A0+D94_x0019_p ... .plt:0000D69C ADRL R12, 0x1076A4 .plt:0000D6A4 LDR PC, [R12, #(system_ptr - 0x1076A4)]! ; __imp_system |
为了分析执行路径,我用了xrefs的功能,先来看看popen的。
执行路径比较简单,再来看看system的。
我整个人都崩溃了……后来我想到用cgi-bin搜索一下关键字,结果真的还有收获。
1
2
|
.text:000110E8 off_110E8 DCD aCgiBin ; DATA XREF: sub_100A0+1808_x0019_r .text:000110E8 ; "cgi-bin/" |
通过这种方法,我找到了比较外层的函数调用sub_100A0,随后终于抓出了一条线。
这次对我这样对路由比较感兴趣的人来说也是一次学习的过程,下面进入对这个漏洞的详细分析。
0x04 命令注入漏洞分析
首先我们下载R系列路由器的固件。
下载地址:http://support.netgear.cn/doucument/detail.asp?id=2251
然后用binwalk -eM来迭代解压这个固件,获得这个固件的squashfs文件系统,这里生成的.squashfs文件需要用7zip来解压。
生成之后用IDA打开/usr/sbin/httpd,跟入sub_100A0函数,在这个函数中有一处调用。
1
|
return sub_19600((const char *)v9, v246, v4); |
这里会调用到sub_19600,其中涉及到三个参数,这里v9是我比较关心的,v9是什么呢,在sub_100A0函数中其实比较容易猜测。
1
2
3
4
5
6
|
if ( !strstr((const char *)v 9 , "unauth.cgi" ) && !strstr((const char *)v 9 , "securityquestions.cgi" ) && !strstr((const char *)v 9 , "passwordrecovered.cgi" ) && !strstr((const char *)v 9 , "userlogin.cgi" ) && !strstr((const char *)v 9 , "multi_login.cgi" ) && (strncmp((const char *)v 9 , "cgi-bin/" , 8 u) || strstr((const char *)v 9 , "RMT_invite_" )) ) |
在这个函数中涉及到大量的strstr子字符串比较,其中比较的内容就是某个常量和v9变量,猜测v9变量就是url的值,这里我们就假设v9的值就是exp的定义 /IP-Addr/cgi-bin/;killall$IFS’httpd’
这里$IFS是Linux的内部域分隔符,这里可以看做是一个空格。
那么接下来跟入sub_19600。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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; } |
比较简短,这里会打印一个url字符串,而url后面跟的%s就是v4,v4由a1而来,a1就是此函数第一个参数,所以第一个参数的确是url的值,接下来v4会作为第三个参数传入sub_36C34函数,漏洞就是在此函数中发生。
这里我先分段讲解这个函数中产生漏洞的整个过程,最后,我再贴上这个函数的完整伪代码。我们重点关注第三个参数v4。
进入后,首先第三个参数,也就是url会交给v6。
1
|
v6 = a3; |
然后会判断v6中是否包含cgi-bin,如果包含,则进入内部处理,这里根据exp,是存在cgi-bin的,接下来进入处理,在处理的过程中,会判断是否包含?,如果包含?,则会给v47赋值,v47这个值我们要记住,在后面设置QUERY_STRING,我们会用到,但是实际上跟此漏洞没有关系。
这里为什么要用到?,就是QUERY_STRING是CGI接收GET参数的,这里默认GET参数是在?后面,就是由此而来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
v12 = strstr(v6, "cgi-bin" ); if ( v12 ) { if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) printf ( "\r\n##########%s(%d)\r\n" , "netgear_commonCgi" , 76); if ( strchr(v12, 63) ) { if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) printf ( "\r\n##########%s(%d)\r\n" , "netgear_commonCgi" , 80); v13 = strchr(v12, 63); if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) printf ( "\r\n#############%s(%d)tmp1=%s,tmp2=%s\r\n" , "netgear_commonCgi" , 83, v12, v13 + 1); strcpy((char *)&v47, v13 + 1); if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) printf ( "\r\n###############%s(%d)query_string=%s\r\n" , "netgear_commonCgi" , 86, &v47); v14 = strchr(v6, 47); |
当然,在exp中是不包含?的,因此这个if ( strchr(v12, 63) )
语句不成立,则不进入这个处理,看一下下面的else语句。
在else语句中会进行字符串切割,切割的就是v12,也就是cgi-bin/;killall,这里注释里我写出了切割后地址指针指向的字符串内容。
比较关心的就是v20,v21和v22,其中由于切割后,后面不再包含47,也就是/的ascii码,因此v22为0,之后会对v50进行初始化。
1
2
3
4
5
6
7
8
9
10
|
else { if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) printf ( "\r\n##########%s(%d)\r\n" , "netgear_commonCgi" , 99); v19 = strchr(v12, 47); v20 = v19 + 1; // ; kill v21 = v19; // /; kill v22 = strchr(v19 + 1, 47); // v22=NULL memset(&v50, 0, 0x40u); // v50init v23 = (char)v21; |
然后就进入一系列的判断,判断的内容就是切割之后;kill后面还包含不包含/。
1
2
3
4
5
6
|
if ( v21 ) v23 = 1; v24 = v22 == 0; if ( v22 ) v24 = v21 == 0; if ( v24 ) |
这里显然是不包含的,因此v24为NULL,也就是v22的值。因此下面if语句就不通过,那么进入到else处理。
在else处理中,会将v50赋值,赋值的内容就是v20中从后往前数5字节,也就是;kill,这样v50就获得了我们的命令。可以看到这个过程没有任何过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
else { strncpy((char *)&v50, v20, v22 - 1 - v21); // v50=; kill if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) printf ( "\r\n#############tmp1=%s,tmp2=%s,tmp3=%s,cgi=%s\r\n" , v12, v21, v22, &v50); v15 = &v46; strcpy((char *)&v46, v22); if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) { v16 = "\r\n###############%s(%d)path_info=%s\r\n" ; v17 = 110; v18 = "netgear_commonCgi" ; goto LABEL_34; } |
随后会进入连续的goto跳转,跳转过程中主要还是打印一些信息,随后会进入到v7处理,v7处理代码挺长的,其中涉及到了QUERY_STRING环境变量的赋值,赋值内容就是v47。当然,我们漏洞的流程,由于没有?,所以不会进入这个流程。
1
2
|
if ( (_BYTE)v47 ) setenv( "QUERY_STRING" , ( const char *)&v47, 1); |
接下来就是漏洞触发的关键位置,由于我们不满足条件,就会执行下面的语句。
1
2
|
v26 = "OPTIONS" ; v27 = (char *)&v53; |
之后出来后会跳转,这里会拷贝v26,也就是OPTIONS到v27中,v27的值就是v53的地址值,之后跳转。
1
2
|
strcpy(v27, v26); goto LABEL_47; |
跳转之后,会进入一系列判断,判断v53的值
1
2
3
4
5
6
7
8
9
10
11
12
|
if ( !strcmp((const char *)&v53, "POST" ) ) { v33 = (const char *)&unk_F062B; v34 = (char *)&v45; } else if ( !strcmp((const char *)&v53, "OPTIONS" ) ) { } else { …… } |
这里省略了一部分过程,由于v53的值是OPTIONS,最后会有一处赋值,v34会赋值为v45的地址值,之后就进入漏洞触发的关键位置,这里会调用sprintf将v50,也就是我们命令的值交给v34。而v34的值就是v45地址的值,这样调用system(&45)的时候,就执行了系统命令。
1
2
3
4
5
6
|
sprintf(v34, v33, &v50); system((const char *)&v45); memset(&v49, 0, 0x40u); memset(&v48, 0, 0x40u); memset(&v51, 0, 0x20u); memset(&v52, 0, 0x10u); |
而在我们分析的过程中,没有一处对这个命令值进行限制,最后导致了命令注入漏洞的发生。下面贴上整个源码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
int __fastcall sub_36C34(const char *a1, int a2, const char *a3, int a4) { v4 = a1; v5 = a2; v6 = a3; v7 = a4; v8 = fork(); v9 = v8; if ( !v8 ) { if ( fork() ) { v10 = v9; goto LABEL_101; } if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) printf ( "\r\n###############%s(%d)url=%s,method=%d\r\n" , "netgear_commonCgi" , 59, v6, v7); v11 = fopen( "/tmp/var/readydropd.conf" , "r" ); if ( v11 ) { fclose(v11); } else { system( "cp -f /www/cgi-bin/readydropd.conf /tmp/var/" ); if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) puts( "\r\n###################copy readydropd.conf\r" ); } v12 = strstr(v6, "cgi-bin" ); if ( v12 ) { if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) printf ( "\r\n##########%s(%d)\r\n" , "netgear_commonCgi" , 76); if ( strchr(v12, 63) ) { if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) printf ( "\r\n##########%s(%d)\r\n" , "netgear_commonCgi" , 80); v13 = strchr(v12, 63); if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) printf ( "\r\n#############%s(%d)tmp1=%s,tmp2=%s\r\n" , "netgear_commonCgi" , 83, v12, v13 + 1); strcpy((char *)&v47, v13 + 1); if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) printf ( "\r\n###############%s(%d)query_string=%s\r\n" , "netgear_commonCgi" , 86, &v47); v14 = strchr(v6, 47); if ( v14 ) { v15 = &v50; memset(&v50, 0, 0x40u); strncpy((char *)&v50, v14 + 1, v13 - 1 - v14); if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) { v16 = "\r\n###############%s(%d)cgi_name=%s\r\n" ; v17 = 93; v18 = "netgear_commonCgi" ; LABEL_34: printf (v16, v18, v17, v15); goto LABEL_40; } } } else { if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) printf ( "\r\n##########%s(%d)\r\n" , "netgear_commonCgi" , 99); v19 = strchr(v12, 47); v20 = v19 + 1; // ; kill v21 = v19; // /; kill v22 = strchr(v19 + 1, 47); // v22=0 memset(&v50, 0, 0x40u); // v50init v23 = (char)v21; if ( v21 ) v23 = 1; v24 = v22 == 0; if ( v22 ) v24 = v21 == 0; if ( v24 ) { if ( v22 ) v25 = 0; else v25 = v23 & 1; if ( v25 ) strcpy((char *)&v50, v20); } else { strncpy((char *)&v50, v20, v22 - 1 - v21); // v50=; kill if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) printf ( "\r\n#############tmp1=%s,tmp2=%s,tmp3=%s,cgi=%s\r\n" , v12, v21, v22, &v50); v15 = &v46; strcpy((char *)&v46, v22); if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) { v16 = "\r\n###############%s(%d)path_info=%s\r\n" ; v17 = 110; v18 = "netgear_commonCgi" ; goto LABEL_34; } } } } LABEL_40: if ( v7 ) { if ( v7 == 1 ) { v26 = "POST" ; v27 = (char *)&v53; } else { if ( v7 != 2 ) { LABEL_47: if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) printf ( "\r\n###############%s(%d)request_method=%s\r\n" , "netgear_commonCgi" , 130, &v53); if ( (_BYTE)v46 ) setenv( "PATH_INFO" , (const char *)&v46, 1); if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) { v28 = getenv( "PATH_INFO" ); printf ( "\r\n###############%s(%d)PATH_INFO=%s\r\n" , "netgear_commonCgi" , 136, v28); } setenv( "LD_LIBRARY_PATH" , "/usr/lib" , 1); if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) { v29 = getenv( "LD_LIBRARY_PATH" ); printf ( "\r\n###############%s(%d)LD_LIBRARY_PATH=%s\r\n" , "netgear_commonCgi" , 140, v29); } setenv( "REQUEST_METHOD" , (const char *)&v53, 1); if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) { v30 = getenv( "REQUEST_METHOD" ); printf ( "\r\n###############%s(%d)REQUEST_METHOD=%s\r\n" , "netgear_commonCgi" , 144, v30); } if ( (_BYTE)v47 ) setenv( "QUERY_STRING" , (const char *)&v47, 1); if ( !strcmp((const char *)&v53, "POST" ) ) { v31 = fopen( "/tmp/post_result" , "r" ); if ( v31 ) { fclose(v31); system((const char *)&unk_F05D8); if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) puts( "\r\n##########del post ############\r" ); } system( "rm -f /tmp/post_data.txt" ); sleep (1u); v32 = fopen( "/tmp/post_data.txt" , "w" ); if ( v32 ) { fputs(v4, v32); fclose(v32); } v33 = (const char *)&unk_F062B; v34 = (char *)&v45; } else if ( !strcmp((const char *)&v53, "OPTIONS" ) ) { v35 = fopen( "/tmp/options_result" , "r" ); if ( v35 ) { fclose(v35); system( "rm -f /tmp/options_result" ); if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) puts( "\r\n##########del option ############\r" ); } v33 = (const char *)&unk_F06A2; v34 = (char *)&v45; } 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); //key !!! memset(&v49, 0, 0x40u); memset(&v48, 0, 0x40u); memset(&v51, 0, 0x20u); memset(&v52, 0, 0x10u); if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) printf ( "\r\n###############%s(%d)\r\n" , "netgear_commonCgi" , 200); if ( !strcmp((const char *)&v53, "POST" ) ) { v37 = "/tmp/post_result" ; } else if ( !strcmp((const char *)&v53, "OPTIONS" ) ) { v37 = "/tmp/options_result" ; } else { v37 = "/tmp/cgi_result" ; } v38 = fopen(v37, "r" ); if ( v38 ) { if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) printf ( "\r\n###############%s(%d)\r\n" , "netgear_commonCgi" , 211); while ( fgets((char *)&v44, 0xFFFF, v38) ) { if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) printf ( "\r\n###############%s(%d)\r\n" , "netgear_commonCgi" , 215); v39 = strstr((const char *)&v44, "Status:" ); if ( v39 ) { strcpy((char *)&v49, v39 + 7); v40 = strchr((const char *)&v49, 10); if ( v40 ) *v40 = 0; if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) printf ( "\r\n###############%s(%d)status=%s\r\n" , "netgear_commonCgi" , 223, &v49); sprintf((char *)&v43, "HTTP/1.1%s\r\n" , &v49); } else { strcat((char *)&v43, (const char *)&v44); } } fclose(v38); } strcat((char *)&v43, "\r\n" ); if ( acosNvramConfig_match((int)&unk_F0378, (int) "1" ) ) printf ( "\r\n###############%s(%d)http_hdr=%s\r\n" , "netgear_commonCgi" , 276, &v43); v41 = strlen((const char *)&v43); sub_F9E0(v5, &v43, v41, 0); if ( acosNvramConfig_match((int)&unk_F0378, (int) "2" ) ) printf ( "\r\n######======######%s(%d)\r\n" , "netgear_commonCgi" , 280); v10 = 0; LABEL_101: exit (v10); } v26 = "OPTIONS" ; v27 = (char *)&v53; } } else { v26 = "GET" ; v27 = (char *)&v53; } strcpy(v27, v26); //key ! goto LABEL_47; } if ( v8 > 0 ) waitpid(v8, &v54, 0); return 0; } |
0x05 补丁对比
NetGear官方在12月14日更新了部分设备的Beta版固件,用来修补这个漏洞,对应设备如下:
链接:http://kb.netgear.com/000036386/CVE-2016-582384
我对这次更新补丁进行了对比分析,发现此次更新主要是针对httpd进行了修补。通过Bindiff进行对比分析。
右侧是补丁前,在之前的分析中,补丁前的函数是sub_36C34,补丁后变成了sub_35EB4,可以看到右侧标红部分修补的位置还是挺多的,接下来查看一下修补的结构,发现在函数入口处有较大不同。
通过Zoom to block查看详细的汇编代码,发现补丁后的httpd,在函数入口处会多出几个strchr判断语句,之后才是fork。
我们通过IDA pro直接跟踪到伪代码部分,结合我之前的分析,可以看到,在v6赋值为a3,也就是exp的url之后,会进行 一些strchr比较。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
int __fastcall sub_36EB4(const char *a1, int a2, const char *a3, int a4) v4 = a1; v5 = a2; v6 = a3; v7 = a4; if ( !strchr(a3, 59) && !strchr(v6, 96) && !strchr(v6, 36) && !strstr(v6, "..") ) { v8 = fork(); v9 = v8; if ( !v8 ) { if ( fork() ) { v10 = v9; goto LABEL_114; } memset(&v47, 0, 0xFFFFu); |
比较的内容就是59、96、36的asii码以及..,59对应的字符是“;”,96对应的字符是“`”,36对应的字符是“$”,如果url里包含这些字符之一的话,则不会进入下面的处理,以此来修补之前exp中;killall$IFS`httpd`带来的命令注入攻击。我个人感觉官网的修复不走心,经过我刚才的分析,发现Beta版固件过滤只针对了;,$,'以及..这四个字符,只过滤了[command];[command]这种情况,并对之后的$IFS等内容进行了过滤,但命令执行拼接也可以通过[command]&&[command],和[command]||[command]方法完成,只是后面拼接的命令进行了一定限制,因此我感觉还有可能存在命令注入漏洞。
0x06 解决方案(针对漏洞版本)
临时方案 :
1.在路由器中禁用远程管理功能
2.指定特定内网IP可以通过WEB方式访问并管理路由器
3.利用这个漏洞,执行关闭WEB服务的命令,不会影响正常上网,但无法使用管理界面,重启路由器后WEB服务可重新启用。命令如下:
1
|
http: //[router-address]/cgi-bin/;killall$IFS'httpd' |
4.如果必要,也可以参照CERT官方建议停止使用该路由器,直到官方发布补丁修复。
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/3287.html
如果此文章侵权,请留言,我们进行删除。0day
文章评论