防止 SQL 注入最有效的方式是使用参数化查询,通过预编译语句实现数据与代码分离,确保用户输入仅作为数据绑定而非 SQL 指令执行。

防止 SQL 注入最有效的方式是使用参数化查询,而不是拼接 SQL 字符串。核心原则是:数据与代码分离,让数据库明确区分“要执行的指令”和“要处理的数据”。
参数化查询:用占位符代替字符串拼接
参数化查询通过预编译语句(Prepared Statement)实现,数据库先解析 SQL 结构,再安全地绑定用户输入值。不同语言写法略有差异,但逻辑一致:
- Python(sqlite3 / psycopg2):用?(SQLite)或%s(PostgreSQL)作占位符,传参时单独提供值列表或字典,不参与 SQL 拼接
- Java(JDBC):使用 PreparedStatement,调用setString()、setInt() 等方法赋值
- C#(ADO.NET):用 @paramName 命名参数,配合 SqlParameter 对象传入
- PHP(PDO):用 ? 或:name占位,调用 execute() 传数组,避免 mysql_query() 等已废弃且无参数支持的函数
避免常见误操作
即使用了参数化,以下做法仍可能引入风险:
- 动态拼接表名、列名、排序字段(如ORDER BY $_GET[‘sort’])——这些无法参数化,需白名单校验
- 在 SQL 中用字符串拼接构造条件(如“WHERE status = ‘” + input + “‘”),哪怕其他部分用了参数
- 把参数值先做 addslashes() 或mysql_real_escape_string()再拼接——这是过时且不可靠的防御方式
- 使用 exec()、eval() 或存储过程中拼接 SQL(如 T -SQL 的 EXEC(@sql))——必须用sp_executesql 配合参数
辅助防护措施
参数化是第一道防线,还需叠加其他实践提升整体安全性:
- 最小权限原则:应用数据库账号仅授予必要权限(如只读表不用 DROP 或GRANT)
- 输入验证:对格式敏感字段(邮箱、手机号、日期)做正则或类型校验,但不能替代参数化
- 错误信息脱敏:关闭数据库详细错误提示(如show_sql_errors = off),避免泄露表结构
- 使用 ORM 时确认其底层是否真正参数化(如 Django ORM 默认安全,但 extra() 或raw()需谨慎)
不推荐的“伪安全”做法
有些方式看似过滤,实则容易绕过或破坏逻辑:
- 仅过滤单引号(‘)、分号(;)、注释符(—、/*)——Unicode 编码、宽字节注入、大小写混用可绕过
- 用 htmlspecialchars() 处理输入后存入数据库——这是针对 XSS 的,对 SQL 注入无效
- 前端 JavaScript 校验——完全可被绕过,服务端必须独立校验
- 依赖 WAF 规则拦截 SQL 关键字——规则滞后、易误杀、无法覆盖所有变种