sql注入防护必须禁用字符串拼接,严格使用参数化查询(如preparedstatement的setxxx()),动态表名/列名需白名单校验,like和json字段操作也须参数化,waf和orm不能替代编码规范。

SQL 注入靠拼接字符串就完事?WHERE name = ' + user_input + ' 是最危险的起点
只要把用户输入直接插进 SQL 字符串里,不管用单引号、双引号还是反引号包着,都等于给攻击者开了后门。数据库不会管你这串字符是人输的还是脚本吐的,它只认语法——而恶意输入能轻易把 WHERE 变成 WHERE 1=1; DROP TABLE users;。
常见错误现象:MySQLSyntaxErrorException 突然变多但不是语法错、后台日志里出现 UNION SELECT 或 AND 1=1 这类模式、某些输入(比如 ' OR '1'='1)让登录绕过或列表全量返回。
- 永远不用字符串拼接构造查询条件,哪怕加了
escape或正则过滤也不行——绕过方法太多 - 所有外部输入(URL 参数、表单字段、Header、Cookie)都默认不可信,不区分“看起来安全”的值
- ORM 框架如 Hibernate、Sequelize 默认启用参数化,但一旦调用
createNativeQuery或raw(),就退回高危区
PreparedStatement 是底线,但得用对 setString() 而不是 executeUpdate(sql)
Java 里写 conn.prepareStatement("SELECT * FROM users WHERE id = ?") 只是第一步;真正起作用的是后续的 ps.setString(1, userInput)。如果漏掉这步,或者错误地把整个 SQL 当作参数传进去(比如 ps.setString(1, "SELECT * FROM users WHERE id = 123")),防护就形同虚设。
使用场景:增删改查所有带变量的语句,包括批量操作(addBatch() 配合 setXxx())和存储过程调用(CallableStatement 同理)。
- 问号占位符位置必须与
setXxx()调用顺序严格一致,索引从 1 开始,不是 0 -
setObject()在类型模糊时可能触发隐式转换,优先用setString()、setLong()等明确类型的方法 - MyBatis 的
#{}默认走预编译,但${}是直接字符串替换——后者仅限表名、排序字段等无法参数化的极少数场景
动态表名/列名没法用 ? 占位?那就白名单校验 + 正则硬约束
像 ORDER BY ? 或 SELECT * FROM ? 会报错,因为预编译只支持值,不支持结构。这时候不能妥协用 String.format 或 concat,得换思路。
性能影响几乎为零,但漏掉校验就是致命漏洞。例如按字段排序,前端传 sort=created_at DESC,后端不能只截取空格前部分,得完整匹配允许的字段+方向组合。
- 定义明确的白名单:
Set.of("id", "name", "email", "created_at"),输入必须完全命中才放行 - 排序方向单独校验:
if (!"ASC".equals(dir) && !"DESC".equals(dir)) throw new IllegalArgumentException(); - 表名若需动态,用枚举或配置项映射,禁止任何自由文本参与拼接,连
toLowerCase().replaceAll("[^a-z0-9_]", "")都不够安全
WAF 和 ORM 自动防护不是保险箱,LIKE 查询和 JSON 字段照样中招
很多团队以为上了 MyBatis Plus 或云 WAF 就高枕无忧,结果在模糊搜索里写 WHERE name LIKE '%${keyword}%',或者对 PostgreSQL 的 jsonb 字段用 ->> 提取后再拼接——这两处都是经典盲点。
原因在于:LIKE 的通配符 % 和 _ 是 SQL 语法的一部分,不是数据值;JSON 提取操作符也属于查询结构,不是参数上下文。
-
LIKE参数必须走setParameter,且通配符由代码添加:ps.setString(1, "%" + userInput + "%"),不是 SQL 里写死LIKE ?然后传入带 % 的字符串 - PostgreSQL 中
WHERE data->>'name' = ?安全,但WHERE data @> '{"name": "' || ? || '"}'不安全 - 数据库函数调用(如
CONCAT()、DATE_ADD())内部若拼接用户输入,同样失效——函数只是语法糖,不改变执行模型
最麻烦的地方往往不在主查询,而在子查询、CTE、视图定义、触发器逻辑里混入了拼接逻辑。一次审计要扫全链路,不能只盯 DAO 层。










