本文目录导读:

报错注入是一种常见的SQL注入攻击方式,攻击者通过构造特殊的输入,使数据库产生包含敏感信息的错误信息,从而获取数据库结构、数据,甚至控制服务器。
修复报错注入的核心思路是:截断错误信息的输出 + 预编译SQL语句。
以下是针对不同场景的详细修复方案和代码案例。
核心原则:使用参数化查询(预编译)
这是最根本、最有效的防御措施,它从逻辑上杜绝了SQL语句结构被篡改的可能性。
👎 漏洞代码示例 (PHP + MySQLi)
// 直接拼接用户输入到SQL语句中
$username = $_GET['username'];
$sql = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($conn, $sql);
if (!$result) {
// 直接输出数据库错误信息,导致报错注入
die('数据库错误: ' . mysqli_error($conn));
}
👍 修复代码 (使用预编译语句)
// 1. 使用预处理语句 (Prepared Statement)
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ?");
// 2. 绑定参数(明确指定类型:s=字符串, i=整数, d=双精度)
$stmt->bind_param("s", $_GET['username']);
// 3. 执行查询
$stmt->execute();
// 4. 获取结果
$result = $stmt->get_result();
// 5. 避免输出原始数据库错误
if (!$result) {
// 记录日志,但不向用户显示具体错误
error_log("数据库查询失败:" . $stmt->error);
die('服务器内部错误,请联系管理员。'); // 返回通用错误提示
}
修复方法针对不同场景
动态构建WHERE条件(MyBatis / JDBC)
漏洞:使用 符号直接拼接SQL片段。
修复:使用 符号,它会生成预编译参数。
MyBatis Mapper XML 示例:
<!-- 漏洞写法(危险) -->
<select id="getUser" resultType="User">
SELECT * FROM users WHERE username = '${username}'
</select>
<!-- 修复写法(安全) -->
<select id="getUser" resultType="User">
SELECT * FROM users WHERE username = #{username}
</select>
IN查询(动态列表长度)
漏洞:循环拼接 IN (’a’, ‘b’)。
修复:使用集合或数组参数。
Java JDBC 示例:
// 漏洞写法
String sql = "SELECT * FROM users WHERE id IN (" + ids.join(",") + ")";
// 修复写法 (使用PreparedStatement + 循环占位符)
StringBuilder sql = new StringBuilder("SELECT * FROM users WHERE id IN (");
List<Integer> idList = Arrays.asList(1, 2, 3, 4, 5);
for (int i = 0; i < idList.size(); i++) {
sql.append("?");
if (i < idList.size() - 1) {
sql.append(",");
}
}
sql.append(")");
PreparedStatement pstmt = connection.prepareStatement(sql.toString());
for (int i = 0; i < idList.size(); i++) {
pstmt.setInt(i + 1, idList.get(i));
}
ResultSet rs = pstmt.executeQuery();
存储过程
漏洞:存储过程内部拼接SQL字符串并执行(动态SQL)。
修复:在存储过程内部也使用参数化查询或严格过滤输入。
SQL Server 存储过程修复示例:
-- 漏洞存储过程
CREATE PROCEDURE GetUser
@username NVARCHAR(50)
AS
BEGIN
DECLARE @sql NVARCHAR(MAX)
-- 危险:拼接用户输入
SET @sql = 'SELECT * FROM users WHERE username = ''' + @username + ''''
EXEC sp_executesql @sql
END
-- 修复存储过程 (使用sp_executesql参数化)
CREATE PROCEDURE GetUserSafe
@username NVARCHAR(50)
AS
BEGIN
DECLARE @sql NVARCHAR(MAX)
DECLARE @Params NVARCHAR(MAX)
SET @sql = 'SELECT * FROM users WHERE username = @User'
SET @Params = '@User NVARCHAR(50)'
-- 安全:通过参数传递
EXEC sp_executesql @sql, @Params, @User = @username
END
辅助防御策略(纵深防御)
即使使用了预编译,也建议采取以下措施:
关闭数据库错误信息输出(生产环境)
这是最直接的防御手段,攻击者看不到错误信息,就无法利用报错注入。
- PHP (config):
display_errors = Off,使用log_errors = On。 - .NET (Web.config):
<customErrors mode="RemoteOnly" />或<deployment retail="true" />。 - Nignx/Apache:配置404/500自定义错误页面,不暴露文件路径和SQL错误。
- MySQL:确保用户权限最小化,取消
SHOW DATABASES,SHOW TABLES等权限。
输入验证与白名单
对于某些无法使用预编译的场景(如排序字段、表名、字段名),只能使用严格的白名单验证。
示例 (排序字段):
// 漏洞:直接拼接 ORDER BY 后的字段
$order = $_GET['order'];
$sql = "SELECT * FROM users ORDER BY $order";
// 修复:使用白名单
$allowedColumns = ['id', 'username', 'email', 'created_at'];
$order = $_GET['order'];
if (!in_array($order, $allowedColumns)) {
$order = 'id'; // 默认值
}
$sql = "SELECT * FROM users ORDER BY $order";
注意:白名单是唯一安全的方式,任何黑名单或正则过滤都容易被绕过。
Web应用防火墙(WAF)
- 如ModSecurity、AWS WAF、Cloudflare WAF。
- 可以拦截常见的报错注入payload(如 , ,
OR,AND,1=1等)。 - 局限性:无法拦截所有变种,特别是编码后的参数。
最小权限原则
- 数据库连接账号只授予必要的权限,只给
SELECT,INSERT,UPDATE,DELETE权限,禁止DROP,CREATE,FILE等权限。 - 如果不需要,不要用
root或sa连接应用。
验证修复效果
修复后,建议进行以下测试来确认漏洞已修复:
- 输入测试payload:
' OR 1=1 --' AND 1=CONVERT(int, (SELECT @@version)) --' UNION SELECT 1,2,3 --
- 观察响应:
- 如果返回通用错误页面(如“服务器内部错误”),没有SQL语句、数据库版本等详细信息,说明修复成功。
- 如果页面依旧白屏且无错误,或者操作正常返回,说明参数化查询生效。
| 修复步骤 | 方法 | 优先级 |
|---|---|---|
| 根本修复 | 参数化查询(预编译) | 必须 |
| 切断信息 | 关闭错误信息输出,返回通用错误提示 | 必须 |
| 辅助加固 | 输入验证(白名单) + 最小权限 | 强烈建议 |
| 兜底防御 | WAF + 日志监控 | 建议 |
一句话总结:永远不要信任用户的任何输入,永远不要拼接SQL字符串。