DIR-816的用户模拟与栈溢出漏洞复现
CVE-2025-5623
在 D-Link 的 DIR-816 1.10CNB05 设备中发现了一个漏洞,该漏洞已被评定为严重级别。此漏洞影响了文件 /goform/qosClassifier 中的 qosClassifier 功能。对参数 dip_address/sip_address 的操作会导致栈溢出漏洞。有可能通过远程发起攻击。该漏洞的利用方法已向公众公布,并且可能被利用。此漏洞仅影响那些已不再由维护方支持的产品。
启动程序
提取固件
拿到固件,查看是否加密
binwalk -E DIR-816A2_FWv1.10CNB05_R1B011D88210.img
没有加密直接用binwalk提取。
binwalk -Me DIR-816A2_FWv1.10CNB05_R1B011D88210.img
启动项分析
启动项分析是找到系统启动时自动运行的程序、找到能够启动我们程序的Web服务器,通常查看/etc/rcS
脚本。若/etc
找不到,可查看/etc_ro
。常见的Web服务器有httpd
、goahead
、mini_httpd
等。
在启动脚本rcS发现有goahead二进制文件,并且在后台运行程序。
我们现在通过下面的命令找到web服务器。找到bin/goahead。像这种web服务器一般都是二进制文件,在bin目录或者sbin目录下。
grep -ir "goahead"
也可以用firmwalker工具搜集信息
用readelf指令查看一下,goahead是mips架构小端序。
模拟程序
这里有两种模拟方案,手动进行qemu系统模拟或者qemu用户模拟。我们选择qemu用户模拟单个程序,这种方法不需要配置整个系统需要的文件、网卡等,仅需要绕过一些判断环境的程序(因为我们没有完整系统),但是模拟起来的程序缺少一些支撑,后续工作会更困难。
复制qemu-mipsel-static到当前squashfs-root文件系统下
cp $(which qemu-mipsel-static) ./
启动goahead程序
sudo chroot . ./qemu-mipsel-static ./bin/goahead
程序运行报错,没有/dev/nvram文件,选择创建文件解决。
下面还有个goahead.c: cannot open pid file这个错误,我们打开ida分析解决
根据IDA分析,v4等于0,会跳转到报错程序。分析函数,fopen需要打开文件,打不开1就返回0,系统报错是打不开文件,因为我们var/run目录下没有goahead.pid文件,因此我们在他指定的目录下创建一个文件。
继续运行,发现程序一直在等待运行,根据输出信息定位到程序停留的地方
分析一下,函数是while(1),要找到break跳出循环,需要v1等于1。我们同样创建这个指定目录下的文件。
重新启动程序,发现还是有报错,我们继续复制报错内容,在ida里面搜索
动态调试,这里选择修改汇编代码里$v0
的值改变跳转方向
修改完,登录服务就启动了。输入我们本机的ip地址加路径/dir_login.asp就可以进入登录页面
现在我们同样需要绕过账号密码的验证,我们在ida里面找到对应的登录函数。
研究登录逻辑发现只需要账号、密码为空就可以实现登录。
这里有两种处理方式
一种使用burp抓包,修改账户和密码为空,把show_username的值删除
一种还是在ida里面判断账号的地方,下断点修改值,令$v0
=0
这里将修改断点的启动程序整理成一个python脚本,方便多次pwndbg调试。其中登录账号需要在dir_login.asp手动输入任意值并点击登录。
import pexpect
import time
import sys
child = pexpect.spawn('pwndbg ./bin/goahead', encoding='utf-8')
PROMPT = 'pwndbg>'
def send_expect(cmd, expect_prompts=None, delay=0.5, description=None):
if description:
print(f"[*] 执行:{description}")
if expect_prompts is None:
expect_prompts = ['pwndbg>', r'Breakpoint \d+,', r'Continuing\.']
child.sendline(cmd)
try:
index = child.expect(expect_prompts, timeout=30)
time.sleep(delay)
return index
except pexpect.exceptions.TIMEOUT:
print(f"[!] 超时:执行命令 `{cmd}` 后未匹配期望提示符。")
print(f"[!] GDB输出如下(最后200字符):\n{child.before[-200:]}")
sys.exit(1)
def confirm_register_value(reg_name, expected_value):
"""检查寄存器值是否设置成功"""
child.sendline(f'print ${reg_name}')
child.expect(PROMP