Skip to content

MySQL SQL 注入防护

攻击原理

SQL 注入的基本概念

SQL 注入是一种常见的 Web 应用安全漏洞,攻击者通过在用户输入中插入恶意 SQL 代码,使应用程序执行非预期的数据库操作。常见的攻击场景包括:

  • 登录绕过
  • 数据窃取
  • 数据篡改
  • 数据库服务器提权

攻击示例

登录绕过攻击

sql
-- 原始查询
SELECT * FROM users WHERE username = 'admin' AND password = 'password';

-- 注入攻击
SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'anything';

数据窃取攻击

sql
-- 原始查询
SELECT * FROM products WHERE id = '1';

-- 注入攻击
SELECT * FROM products WHERE id = '1' UNION SELECT username, password FROM users;

代码层面防护

参数化查询

使用预处理语句

PHP 示例

php
// 不安全的做法
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

// 安全的做法
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);

Java 示例

java
// 不安全的做法
String username = request.getParameter("username");
String password = request.getParameter("password");
String query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";

// 安全的做法
PreparedStatement stmt = connection.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?");
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();

输入验证

类型验证

  • 对数字类型的输入进行数值范围检查
  • 对日期类型的输入进行格式验证
  • 对字符串类型的输入进行长度限制

白名单过滤

php
// 只允许特定字符
function validate_input($input) {
    return preg_match('/^[a-zA-Z0-9_]+$/', $input);
}

转义特殊字符

使用内置转义函数

PHP 示例

php
$username = mysqli_real_escape_string($connection, $_POST['username']);
$password = mysqli_real_escape_string($connection, $_POST['password']);
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

数据库层面防护

最小权限原则

用户权限设置

sql
-- 创建应用专用用户
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'strong_password';

-- 只授予必要的权限
GRANT SELECT, INSERT, UPDATE ON app_db.* TO 'app_user'@'localhost';

-- 禁止危险操作
REVOKE ALTER, DROP, CREATE ON *.* FROM 'app_user'@'localhost';

存储过程使用

优点

  • 封装 SQL 逻辑,减少直接执行 SQL 的机会
  • 可以设置执行权限,限制用户操作
  • 提高性能,减少网络传输

示例

sql
DELIMITER //

CREATE PROCEDURE get_user(IN user_id INT)
BEGIN
    SELECT * FROM users WHERE id = user_id;
END //

DELIMITER ;

-- 调用存储过程
CALL get_user(1);

数据库防火墙

MySQL Enterprise Firewall

  • 基于语句模式的防火墙
  • 学习模式和保护模式
  • 防止未授权的 SQL 语句执行

第三方防火墙

  • ProxySQL
  • MaxScale
  • 应用层 WAF

配置层面防护

禁用危险功能

ini
# my.cnf 配置
[mysqld]
# 禁用 LOAD DATA INFILE
local-infile=0

# 禁用符号链接
symbolic-links=0

# 禁用远程 root 登录
skip-networking=1

错误信息处理

生产环境配置

php
// 开发环境
error_reporting(E_ALL);
ini_set('display_errors', 1);

// 生产环境
error_reporting(0);
ini_set('display_errors', 0);
ini_set('log_errors', 1);

自定义错误页面

  • 对用户显示通用错误信息
  • 详细错误信息只记录到日志
  • 避免在错误信息中泄露数据库结构

监控与审计

审计日志配置

ini
# my.cnf 配置
[mysqld]
# 启用审计日志
audit_log=ON
audit_log_format=JSON
audit_log_file=/var/log/mysql/audit.log

异常 SQL 检测

  • 监控执行时间过长的 SQL 语句
  • 检测大量数据查询操作
  • 识别异常的表访问模式

安全扫描

定期安全评估

  • 使用专业的 SQL 注入扫描工具
  • 进行渗透测试
  • 代码审计

常见工具

  • SQLmap
  • OWASP ZAP
  • Burp Suite

最佳实践

开发规范

  1. 强制使用参数化查询
  2. 实施输入验证
  3. 使用 ORM 框架
  4. 定期代码审计

部署规范

  1. 遵循最小权限原则
  2. 定期更新 MySQL 版本
  3. 配置数据库防火墙
  4. 启用审计日志

应急响应

  1. 建立安全事件响应流程
  2. 定期备份数据库
  3. 制定数据泄露应急预案
  4. 保持安全团队的技术培训

常见问题(FAQ)

Q1: 使用 ORM 框架是否可以完全防止 SQL 注入?

A1: ORM 框架可以大大减少 SQL 注入的风险,但不能完全防止。仍然需要注意:

  • 避免使用原生 SQL 拼接
  • 正确使用 ORM 的参数绑定功能
  • 对复杂查询进行安全审查

Q2: 存储过程是否比直接执行 SQL 更安全?

A2: 存储过程可以提高安全性,但也需要注意:

  • 避免在存储过程中使用动态 SQL
  • 正确处理存储过程的输入参数
  • 限制存储过程的执行权限

Q3: 如何检测已经发生的 SQL 注入攻击?

A3: 可以通过以下方式检测:

  • 分析数据库审计日志中的异常 SQL 语句
  • 监控服务器日志中的可疑访问模式
  • 使用入侵检测系统 (IDS) 进行实时监控
  • 定期检查数据库中的异常数据修改

Q4: 除了技术措施,还有哪些方法可以防止 SQL 注入?

A4: 还可以采取以下措施:

  • 对开发人员进行安全培训
  • 建立安全编码规范
  • 实施代码审查流程
  • 定期进行安全测试和评估
  • 建立安全事件响应机制