参数化查询不能自动解决多条件组合场景下的sql注入风险,必须配合显式条件开关逻辑和参数绑定策略,如用coalesce或is null处理可选字段,orm中使用querywrapper链式调用,order by等非参数化位置须白名单校验,并限制数据库账号权限。

直接用参数化查询不能自动解决多条件组合场景下的 SQL 注入风险,必须配合显式的条件开关逻辑和参数绑定策略。
WHERE 子句中可选字段的参数化写法
当查询支持“品牌可选、价格区间可选、状态可选”这类动态条件时,不能靠字符串拼接追加 AND name = ?,否则空值或未传字段会破坏 SQL 结构。正确做法是把每个可选字段转为带判空逻辑的固定子句。
- 错误示例:
" AND name = '" + name + "'"—— name 为空时语法错误,且存在注入点 - 推荐写法(MySQL):
AND (COALESCE(?, '') = '' OR name = ?),两个?分别绑定空检查值和实际匹配值 - PostgreSQL 可用
AND ($1 IS NULL OR name = $1),但注意驱动是否支持命名/位置参数混用 - ORM 如 MyBatis-Plus 中,应使用
QueryWrapper的like()+or()组合,而非手拼 SQL 字符串
MyBatis-Plus 多数组参数的安全构造方式
前端传 {"brands": ["苹果", "华为"], "statuses": [1, 2]} 这类结构时,in() 和 or().like() 都要确保参数被整体视为一个绑定单元,不能在循环里反复调用 execute() 或拼字符串。
- 避免在 for 循环中拼
"'"+brand+"'",哪怕用了like()方法也要确认底层是否走预编译 - 正确姿势:用
apply()+ 占位符 + 手动传参数组,例如wrapper.apply("brand_name IN ({0})", brandList)(需确认 MyBatis-Plus 版本支持) - 更稳妥的是改用
QueryWrapper的in()和like()链式调用,它们内部已做参数化封装,但要注意or()嵌套层级不要过深导致执行计划退化 - 若必须用原生 SQL,务必用
@SelectProvider+SQL类动态构建,并通过Map传参,禁止字符串插值
ORDER BY / LIMIT / 表名等无法参数化的字段处理
这些位置数据库不支持问号占位,硬塞参数会报错,必须用白名单映射或枚举校验。
- 错误做法:
ORDER BY ${sortField}或ORDER BY ?—— 前者是拼接,后者语法非法 - 正确做法:定义允许排序字段的映射表,如
Map.of("price", "price ASC", "time", "create_time DESC"),再从用户输入中查出对应值 -
LIMIT ?, ?是合法的,但LIMIT ?单参数也支持;不过OFFSET要同步校验是否为非负整数,防止传-1导致全表扫描 - 表名、列名、函数名一律禁用用户输入,必须来自配置或硬编码枚举
最易被忽略的一点:即使所有 WHERE 条件都参数化了,如果没限制数据库账号权限,攻击者仍可能通过 UNION SELECT 拖库——所以 SELECT 权限只给必要表,禁用 information_schema 访问,才是兜底防线。










