OneShell

I fight for a brighter tomorrow

0%

CVE-2024-28353:TEW-827DRU命令执行

漏洞描述

CVE-2024-28353是发生在厂商TRENDNET,设备TEW-827DRU,固件版本2.10B01中的命令执行。攻击者可以通过页面smbserver.asp,向apply.cgi发送数据包,在参数usapps.config.smb_admin_name处执行命令注入。

漏洞相关信息和固件下载地址如下:

漏洞成因

通过漏洞公告搜索字符串usbapps.config.smb_admin_name,可以定位到漏洞发生在程序/www/cgi/ssi,函数FUN_0041af68中,参数usbapps.config.smb_admin_name的值没有经过校验随后送入到system函数中执行,导致命令注入。

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
char * FUN_0041af68(undefined4 param_1)

{
undefined4 post_smb_admin_name;
int post_smb_user;
char *post_smb_enable;
int smb_username;
char acStack_3c [52];

post_smb_user = FUN_00410e4c();
if (post_smb_user == 0) {
post_smb_admin_name = uci_safe_get("usbapps.config.smb_admin_name");
// 此处存在命令注入
_system("deluser %s",post_smb_admin_name);
for (smb_username = 0; smb_username < 0x19; smb_username = smb_username + 1) {
sprintf(acStack_3c,"usbapps.@smb[%d].username",smb_username);
post_smb_user = uci_safe_get(acStack_3c);
if (post_smb_user == 0) break;
_system("smbpasswd -x %s",post_smb_user);
_system("deluser %s",post_smb_user);
}
__post2nvram(param_1);
system("uci commit usbapps");
post_smb_enable = (char *)uci_safe_get("usbapps.config.smb_enable");
post_smb_user = atoi(post_smb_enable);
if (post_smb_user == 1) {
system("/etc/init.d/samba enable");
system("/etc/init.d/samba restart");
}
else {
system("/etc/init.d/samba stop");
system("/etc/init.d/samba disable");
smb_username = 0;
while( true ) {
sprintf(acStack_3c,"samba.@sambashare[%d].name",smb_username);
post_smb_enable = (char *)uci_safe_get(acStack_3c);
if (*post_smb_enable == '\0') break;
_system("uci delete samba.@sambashare[%d]",smb_username);
smb_username = smb_username + 1;
}
system("uci commit samba");
}
sleep(1);
uci_free();
post_smb_enable = (char *)get_response_page();
}
else {
make_back_msg("Read only user cannot save settings.");
post_smb_enable = "error.asp";
}
return post_smb_enable;
}

搜索该函数的交叉引用,没有发现调用地址,猜测是通过回调函数的方式注册。通过分析,在main函数中调用的函数dump_action或者函数dump_apply中有设置根据传入的action调用回函函数的代码,例如在函数dump_action中,可以进一步定位到函数get_handler_by_action(原函数FUN_0043f290)是根据传入的action获取到相关结构体指针。在地址0x004edc50恢复action_handlers结构体数组,数组中的结构体定义大致如下:

1
2
3
4
5
0x0	0x4	addr	pointer	action	具体的action
0x4 0x4 addr pointer unknown0
0x8 0x4 addr pointer unknown1
0xc 0x4 addr pointer handler action对应的处理函数
0x10 0x4 addr pointer unknown2

恢复出来的结构体数组大致如下,此处仅仅展示漏洞函数FUN_0041af68相关的action_handler,其中action=samba36,负责处理的函数是FUN_0041af68。

1
2
3
4
5
6
7
8
9
004ee254 c8 7e 4c 00   action_handler                       [76]
00 00 00 00
00 00 00 00
004ee254 c8 7e 4c 00 addr s_samba36_004c7ec8 action 具体的action
004ee258 00 00 00 00 addr 00000000 unknown0
004ee25c 00 00 00 00 addr 00000000 unknown1
004ee260 68 af 41 00 addr FUN_0041af68 handler action对应的处理
004ee264 70 56 4f 00 addr PTR_s_usbapps.confi unknown2 = 004c3514

数据流分析

该程序的web server是/usr/sbin/uhttpd,启动命令如下,可以看到很多文件名后缀例如asp、cgi、js等都是通过/www/cgi/ssi进行处理。

1
/usr/sbin/uhttpd -f -h /www -r TEW-827DRU -x /cgi-bin -t 120 -T 30 -A 1 -n 64 -D -R -p 0.0.0.0:80 -p [::]:80 -i .sh /bin/sh -i .asp /www/cgi/ssi -i .cgi /www/cgi/ssi -i .js /www/cgi/ssi -i .xml /www/cgi/ssi -i .txt /www/cgi/ssi -v /public_tew.js -v /uk*.js -v /reject.asp -v /apply_sec.cgi -v /test_mode.cgi -v /log_clear_page.cgi -v /cgi-bin/luci -v /cgi-bin/ozker -v /luci-static/* -v /js/* -v /chklst.txt -I login_pic.asp -C /etc/uhttpd.crt -K /etc/uhttpd.key -s 0.0.0.0:443 -s [::]:443

在程序uhttpd中,通过fork + execl的方式,根据请求文件后缀,使用环境变量和标准数据将待处理数据传入到/www/cgi/ssi中,代码位于函数uh_cgi_request_with_ssc中:

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
undefined4 uh_cgi_request_with_ssc(int *param_1,astruct *script_struct,char *cgi_name,int param_4)

{
......
/* 重定向 */
iVar1 = pipe(&local_8c);
if ((iVar1 < 0) || (iVar1 = pipe(&local_94), iVar1 < 0)) {
if (0 < local_8c) {
close(local_8c);
}
if (0 < local_88) {
close(local_88);
}
if (0 < local_94) {
close(local_94);
}
if (0 < local_90) {
close(local_90);
}
......
}
/* 使用fork创建子进程 */
_Var3 = fork();
if (_Var3 == -1) {
piVar2 = __errno_location();
tmp_env_value = strerror(*piVar2);
pcVar9 = "Failed to fork child: %s\n";
goto ERROR_500;
}
......
/* 设置相关环境变量 */
clearenv();
setenv("GATEWAY_INTERFACE","CGI/1.1",1);
setenv("SERVER_SOFTWARE","uHTTPd",1);
setenv("PATH","/sbin:/usr/sbin:/bin:/usr/bin",1);
if (*param_1 != 0) {
setenv("HTTPS","on",1);
}
piVar2 = param_1 + 0x4a2;
tmp_env_value = (char *)sa_straddr(piVar2);
setenv("SERVER_NAME",tmp_env_value,1);
tmp_env_value = (char *)sa_straddr(piVar2);
setenv("SERVER_ADDR",tmp_env_value,1);
piVar10 = param_1 + 0x4a9;
tmp_env_value = (char *)sa_strport(piVar2);
setenv("SERVER_PORT",tmp_env_value,1);
tmp_env_value = (char *)sa_straddr(piVar10);
setenv("REMOTE_HOST",tmp_env_value,1);
tmp_env_value = (char *)sa_straddr(piVar10);
setenv("REMOTE_ADDR",tmp_env_value,1);
tmp_env_value = (char *)sa_strport(piVar10);
setenv("REMOTE_PORT",tmp_env_value,1);
setenv("SCRIPT_NAME",script_struct->script_name,1);
setenv("SCRIPT_FILENAME",script_struct->script_filename,1);
setenv("DOCUMENT_ROOT",script_struct->document_root,1);
tmp_env_value = script_struct->envs;
if (tmp_env_value == (char *)0x0) {
tmp_env_value = "";
}
setenv("QUERY_STRING",tmp_env_value,1);
if (script_struct->field3_0xc != (char *)0x0) {
setenv("PATH_INFO",script_struct->field3_0xc,1);
}
if (param_1[0x41d] == 0x194) {
tmp_env_value = "404";
}
else {
tmp_env_value = "200";
}
setenv("REDIRECT_STATUS",tmp_env_value,1);
setenv("SERVER_PROTOCOL",*(char **)(http_versions + param_1[0x41c] * 4),1);
setenv("REQUEST_METHOD",(&http_methods)[param_1[0x41b]],1);
setenv("REQUEST_URI",(char *)param_1[0x41e],1);
if (param_1[0x45f] != 0) {
setenv("REMOTE_USER",(char *)(param_1[0x45f] + 0x1000),1);
}
ppcVar11 = (char **)(param_1 + 0x420);
iVar1 = 0;
do {
local_30 = ppcVar11[-1];
if (local_30 == (char *)0x0) break;
iVar5 = strcasecmp(local_30,"Accept");
if (iVar5 == 0) {
tmp_env_value = "HTTP_ACCEPT";
LAB_00408ea8:
setenv(tmp_env_value,*ppcVar11,1);
}
else {
iVar5 = strcasecmp(local_30,"Accept-Charset");
if (iVar5 == 0) {
tmp_env_value = "HTTP_ACCEPT_CHARSET";
goto LAB_00408ea8;
}
iVar5 = strcasecmp(local_30,"Accept-Encoding");
if (iVar5 == 0) {
tmp_env_value = "HTTP_ACCEPT_ENCODING";
goto LAB_00408ea8;
}
iVar5 = strcasecmp(local_30,"Accept-Language");
if (iVar5 == 0) {
tmp_env_value = "HTTP_ACCEPT_LANGUAGE";
goto LAB_00408ea8;
}
iVar5 = strcasecmp(local_30,"Authorization");
if (iVar5 == 0) {
tmp_env_value = "HTTP_AUTHORIZATION";
goto LAB_00408ea8;
}
iVar5 = strcasecmp(local_30,"Connection");
if (iVar5 == 0) {
tmp_env_value = "HTTP_CONNECTION";
goto LAB_00408ea8;
}
iVar5 = strcasecmp(local_30,"Cookie");
if (iVar5 == 0) {
tmp_env_value = "HTTP_COOKIE";
goto LAB_00408ea8;
}
iVar5 = strcasecmp(local_30,"Host");
if (iVar5 == 0) {
tmp_env_value = "HTTP_HOST";
goto LAB_00408ea8;
}
iVar5 = strcasecmp(local_30,"Referer");
if (iVar5 == 0) {
tmp_env_value = "HTTP_REFERER";
goto LAB_00408ea8;
}
iVar5 = strcasecmp(local_30,"User-Agent");
if (iVar5 == 0) {
tmp_env_value = "HTTP_USER_AGENT";
goto LAB_00408ea8;
}
iVar5 = strcasecmp(local_30,"Content-Type");
if (iVar5 == 0) {
tmp_env_value = "CONTENT_TYPE";
goto LAB_00408ea8;
}
iVar5 = strcasecmp(local_30,"Content-Length");
if (iVar5 == 0) {
tmp_env_value = "CONTENT_LENGTH";
goto LAB_00408ea8;
}
}
iVar1 = iVar1 + 2;
ppcVar11 = ppcVar11 + 2;
} while (iVar1 != 0x40);
......
/* 根据cgi_name传递参数调用execl */
if (cgi_name == (char *)0x0) {
execl(script_struct->script_filename,script_struct->script_filename,0);
cgi_name = script_struct->script_filename;
}
else {
memset(acStack_54,0,0x24);
memset(acStack_78,0,0x24);
tmp_env_value = getenv("SERVER_PORT");
iVar1 = strcmp(tmp_env_value,"80");
if (iVar1 == 0) {
tmp_env_value = getenv("REQUEST_METHOD");
iVar1 = strcmp(tmp_env_value,"POST");
if ((iVar1 != 0) || (iVar1 = strcmp((char *)param_1[0x41e],"/test_mode.cgi"), iVar1 == 0))
goto LAB_00408fac;
LAB_00409004:
__stream = fopen("/tmp/csrf_hash","r");
if (__stream == (FILE *)0x0) {
iVar1 = strncmp((char *)param_1[0x41e],"/apply_sec.cgi",0xd);
LAB_00409154:
if (iVar1 == 0) goto EXEC_SCRIPT;
}
else {
fgets(acStack_78,0x24,__stream);
sVar6 = strlen(acStack_78);
acStack_78[sVar6 - 1] = '\0';
fscanf(__stream,"%lld",&local_80);
fclose(__stream);
unlink("/tmp/csrf_hash");
time((time_t *)&local_98);
uVar4 = (uint)(local_98 < local_98 - local_80);
uVar8 = ((int)local_98 >> 0x1f) - local_7c;
if (((int)(uVar8 - uVar4) < 1) && ((uVar8 != uVar4 || (local_98 - local_80 < 0x12d)))) {
tmp_env_value = getenv("QUERY_STRING");
tmp_env_value = strstr(tmp_env_value,"csrf_token");
if (tmp_env_value != (char *)0x0) {
strncpy(acStack_54,tmp_env_value + 0xb,0x20);
iVar1 = strcmp(acStack_54,acStack_78);
goto LAB_00409154;
}
}
}
......
EXEC_SCRIPT:
if (param_4 == 0) {
execl(cgi_name,cgi_name,script_struct->script_filename,0);
}
else {
......
execl(cgi_name,cgi_name,"-n",script_struct->script_filename,0);
}
}
exit(0);
}

通过动态调试可以查看到/www/cgi/ssi启动的参数和环境变量如下,此时是登录数据包触发:

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
pwndbg> stack 40
00:0000│ sp 0x7fd4b7a0 ◂— 0x2
01:0004│ 0x7fd4b7a4 —▸ 0x7fd4bbe0 ◂— '/www/cgi/ssi'
02:0008│ 0x7fd4b7a8 —▸ 0x7fd4bbed ◂— '/www/apply_sec.cgi'
03:000c│ 0x7fd4b7ac ◂— 0x0
04:0010│ 0x7fd4b7b0 —▸ 0x7fd4bc00 ◂— 'GATEWAY_INTERFACE=CGI/1.1'
05:0014│ 0x7fd4b7b4 —▸ 0x7fd4bc1a ◂— 'SERVER_SOFTWARE=uHTTPd'
06:0018│ 0x7fd4b7b8 —▸ 0x7fd4bc31 ◂— 'PATH=/sbin:/usr/sbin:/bin:/usr/bin'
07:001c│ 0x7fd4b7bc —▸ 0x7fd4bc54 ◂— 'SERVER_NAME=192.168.10.1'
08:0020│ 0x7fd4b7c0 —▸ 0x7fd4bc6d ◂— 'SERVER_ADDR=192.168.10.1'
09:0024│ 0x7fd4b7c4 —▸ 0x7fd4bc86 ◂— 'SERVER_PORT=80'
0a:0028│ 0x7fd4b7c8 —▸ 0x7fd4bc95 ◂— 'REMOTE_HOST=192.168.10.2'
0b:002c│ 0x7fd4b7cc —▸ 0x7fd4bcae ◂— 'REMOTE_ADDR=192.168.10.2'
0c:0030│ 0x7fd4b7d0 —▸ 0x7fd4bcc7 ◂— 'REMOTE_PORT=42542'
0d:0034│ 0x7fd4b7d4 —▸ 0x7fd4bcd9 ◂— 'SCRIPT_NAME=/apply_sec.cgi'
0e:0038│ 0x7fd4b7d8 —▸ 0x7fd4bcf4 ◂— 'SCRIPT_FILENAME=/www/apply_sec.cgi'
0f:003c│ 0x7fd4b7dc —▸ 0x7fd4bd17 ◂— 'DOCUMENT_ROOT=/www'
10:0040│ 0x7fd4b7e0 —▸ 0x7fd4bd2a ◂— 'QUERY_STRING=csrf_token=l6E0vYH10z748RVAnr1p1uk5pxUK0Tcr'
11:0044│ 0x7fd4b7e4 —▸ 0x7fd4bd63 ◂— 'REDIRECT_STATUS=200'
12:0048│ 0x7fd4b7e8 —▸ 0x7fd4bd77 ◂— 'SERVER_PROTOCOL=HTTP/1.1'
13:004c│ 0x7fd4b7ec —▸ 0x7fd4bd90 ◂— 'REQUEST_METHOD=POST'
14:0050│ 0x7fd4b7f0 —▸ 0x7fd4bda4 ◂— 'REQUEST_URI=/apply_sec.cgi?csrf_token=l6E0vYH10z748RVAnr1p1uk5pxUK0Tcr'
15:0054│ 0x7fd4b7f4 —▸ 0x7fd4bdeb ◂— 'HTTP_HOST=192.168.10.1'
16:0058│ 0x7fd4b7f8 —▸ 0x7fd4be02 ◂— 'CONTENT_LENGTH=154'
17:005c│ 0x7fd4b7fc —▸ 0x7fd4be15 ◂— 'CONTENT_TYPE=application/x-www-form-urlencoded'
18:0060│ 0x7fd4b800 —▸ 0x7fd4be44 ◂— 'HTTP_USER_AGENT=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
19:0064│ 0x7fd4b804 —▸ 0x7fd4beba ◂— 'HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7'
1a:0068│ 0x7fd4b808 —▸ 0x7fd4bf4e ◂— 'HTTP_REFERER=http://192.168.10.1/smbserver.asp'
1b:006c│ 0x7fd4b80c —▸ 0x7fd4bf7d ◂— 'HTTP_ACCEPT_ENCODING=gzip, deflate'
1c:0070│ 0x7fd4b810 —▸ 0x7fd4bfa0 ◂— 'HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7'
1d:0074│ 0x7fd4b814 —▸ 0x7fd4bfd9 ◂— 'HTTP_CONNECTION=close'
1e:0078│ 0x7fd4b818 ◂— 0x0

在程序/www/cgi/ssi,会调用相关函数__post2nvram_range__post2nvram将从uhttpd传递的参数解析成uci键值对并保存,以供后续代码使用。主要原理是通过函数uci_set_list、uci_set_option来设置uci键值对、通过函数uci_safe_get来获取uci键值对。

因此,对于程序/www/cgi/ssi进行数据流分析,可以重点关注环境变量、uci获取键值这两个关键点做source。

平台复现

通过之前的分析,可以得到数据的一个source点是调用函数uci_safe_get的地方,因此,可以在图数据库中使用如下的语句设置source点:

1
2
3
4
MATCH (n:identifier)
WHERE n.callee CONTAINS 'uci_safe_get' AND n.index = -1
SET n.is_source = 1
RETURN n.function

调用平台的命令执行规则组,修改类system函数第二个参数,可被用户控制中的规则,添加新的一个函数_system,因为在该程序中函数_system的第一个参数是格式化字符串,第二个参数是可控传入变量。
undefined

该漏洞可以被扫描出来。
undefined