详解SQL注入防御代码编写规范与最佳实践
一、核心原则:参数化查询
防御SQL注入最有效的手段是使用参数化查询也称预编译语句。该方法将SQL语句结构与用户输入数据严格分离,数据库引擎会对参数部分自动转义,从根本上杜绝拼接注入的可能。无论使用哪种编程语言,涉及数据库操作时都应将参数化查询作为首选方案。
在Java中使用PreparedStatement代替Statement,在PHP中使用PDO的prepare方法代替mysqli_query,在Python中使用execute方法并传入参数元组。这是安全编码的第一道也是最重要的防线。
二、MyBatis框架安全写法
许多Java项目使用MyBatis作为持久层框架。使用时必须区分井号与美元符号两种占位符的区别。井号占位符使用预编译方式,自动加引号并转义,是安全方式。美元符号占位符直接拼接字符串,存在注入风险,仅在动态表名或列名等少数场景才可使用且必须严格校验传入值。
严禁在SQL映射文件中直接拼接用户输入。建议使用MyBatis的OGNL表达式或编写自定义类型处理器处理特殊需求。Code Review环节应将美元符号的使用列为必查项。
三、输入验证与过滤机制
参数化查询并非万能。数字类型的参数仍应在应用层校验其数据类型和范围。字符串类型的参数应校验长度和字符集。邮箱、手机号等有固定模式的字段应使用正则表达式验证格式。
输入过滤应采取白名单策略而非黑名单。只允许已知安全的字符通过,拒绝其他所有字符。例如数字ID只允许0到9的数字,用户名只允许字母数字和下划线。后端过滤不可依赖前端验证,因为攻击者可以绕过前端直接发送请求。
四、数据库权限最小化
数据库连接账号应遵循最小权限原则。对于只读业务使用只读账号,写操作使用仅拥有INSERT和UPDATE权限的账号。从结构上限制,即使应用层有注入漏洞,攻击者也无法执行DROP、TRUNCATE等高危操作。
存储过程和函数在创建时使用EXECUTE AS指定调用者权限,避免赋予过高的数据库权限。生产和开发环境使用不同的数据库账号,密码使用强密码并定期更换。敏感操作使用独立的数据库账号以方便审计追踪。
五、错误信息处理与日志审计
生产环境不应向用户暴露详细的数据库错误信息。攻击者往往通过分析错误信息来推断数据库结构和注入点。所有数据库异常应在应用层捕获并转换为通用的友好提示。
同时应记录完整的数据库操作日志,包括执行时间、SQL语句带参数、执行用户IP和最终影响行数。日志中禁止记录敏感数据明文。定期分析日志发现异常查询模式,通过慢查询日志排查潜在的注入行为并建立安全事件响应机制。