本文目录导读:

数据库注入(SQL注入)是目前最常见也最危险的Web安全漏洞之一,防御的核心原则是:永远不要信任用户的任何输入。
以下是经过行业验证的、从高到低优先级的防御措施:
核心防御:使用参数化查询(Prepared Statements)
这是防御SQL注入最有效、最根本的方法,它通过将SQL语句的结构与数据分离,确保用户输入永远不被解释为SQL代码。
-
原理:数据库驱动程序会将SQL语句模板(包含占位符)和用户数据分别发送给数据库服务器,数据库先编译SQL模板,再将数据作为纯值绑定,从而杜绝了数据被当作代码执行的可能。
-
代码示例(不同语言):
-
Java (JDBC):
String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1, userInputUsername); pstmt.setString(2, userInputPassword); ResultSet rs = pstmt.executeQuery();
-
Python (PyMySQL/MySQLdb):
cursor = connection.cursor() sql = "SELECT * FROM users WHERE username = %s AND password = %s" cursor.execute(sql, (user_input_username, user_input_password))
-
Node.js (mysql2):
const mysql = require('mysql2/promise'); // 使用 ? 作为占位符 const [rows] = await connection.execute( 'SELECT * FROM users WHERE username = ? AND password = ?', [userInputUsername, userInputPassword] ); -
PHP (PDO):
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username AND password = :password'); $stmt->execute(['username' => $userInputUsername, 'password' => $userInputPassword]);
-
-
注意:很多ORM(如Hibernate、MyBatis、Entity Framework、Django ORM)在底层实现了参数化查询,但必须确保你没有使用拼接字符串的方式(如
select * from table where name='${name}'),或者使用ORM提供的原生SQL接口时未错误地使用拼接。
重要辅助手段:输入验证与清理
虽然参数化查询是主防御,但输入验证可以作为深度防御的一环,防止非SQL注入的其它攻击(如XSS、命令注入)。
- 白名单验证:只允许已知的、安全的字符或格式。
- 示例:如果字段预期是“数字ID”,使用
is_numeric()或intval()强制转换。 - 示例:如果字段预期是“电子邮件”,使用正则表达式验证格式。
- 示例:如果字段预期是“数字ID”,使用
- 黑名单过滤:不推荐作为主要手段,攻击者总能绕过简单的黑名单(如过滤 等),黑名单容易漏掉编码变种(如
%27、0x27、UNION/**/SELECT)。
关键实践:最小权限原则
- 数据库账号权限:
- 应用程序连接数据库的账号,绝不能使用
root或DBA账号。 - 只给予该账户所需的最小权限。
- 常规查询:只给
SELECT、INSERT、UPDATE权限(对于用户表,通常不需要DELETE)。 - 禁止对不需要的表执行访问(如系统表
information_schema)。
- 常规查询:只给
- 严格禁止使用动态执行存储过程的权限(如
EXECUTE在某些数据库中被滥用)。
- 应用程序连接数据库的账号,绝不能使用
限制数据库暴露面
- 禁用危险函数:在数据库中禁用或移除不需要的危险功能,如:
xp_cmdshell(SQL Server)LOAD_FILE、INTO OUTFILE(MySQL)UTL_FILE、DBMS_SCHEDULER(Oracle)
- 错误信息处理:
- 绝对不要将数据库的原始错误信息(如
“You have an error in your SQL syntax...near '...' at line 1”)直接返回给用户。 - 自定义错误页面:在生产环境中,向用户展示通用的“服务器内部错误”或“请求无效”页面,并将详细错误记录到服务器日志中。
- 绝对不要将数据库的原始错误信息(如
高级与补充防御
- Web应用防火墙:作为网络层面的防御,可以拦截明显的攻击载荷(如
OR 1=1、UNION SELECT),但不能替代代码层面的防御。 - 存储过程的安全使用:
- 如果使用存储过程,也必须使用参数化方式调用,而不能在存储过程内部进行字符串拼接。
- 定期安全扫描:使用自动化工具(如SQLMap、商业DAST/SAST工具)或人工代码审计,检测代码中是否存在未被参数化的SQL查询。
防御优先级总结
| 优先级 | 防御措施 | 说明 |
|---|---|---|
| 第一(必须) | 参数化查询/预编译语句 | 根除注入的根基,不可绕过。 |
| 第二(必须) | 最小权限原则 | 限制攻击成功后的损害范围。 |
| 第三(重要) | 输入白名单验证 | 提前拦截异常格式数据,减少攻击面。 |
| 第四(重要) | 禁用危险函数/特性 | 关闭数据库的“后门”。 |
| 第五(推荐) | 安全错误处理 | 避免泄露数据库结构信息。 |
| 第六(辅助) | WAF、定期扫描 | 增加额外的安全层。 |
只要你使用参数化查询(Prepared Statements),并将数据库账户权限限制到最小,那么99%的SQL注入攻击都无法生效。 不要试图通过写复杂的正则或黑名单过滤器来替代参数化查询。