apply()是sql注入高危入口,因其不走预编译流程而直接拼接原始sql;安全写法必须用{0}占位符绑定值,且仅限于值参数,字段名等结构化内容需白名单校验。

apply方法为什么是SQL注入高危入口
apply() 是 QueryWrapper 和 LambdaQueryWrapper 中唯一允许直接插入原始 SQL 片段的方法,它不走 MyBatis 的参数预编译流程(即不自动转成 ? 占位符),而是把传入的字符串原样拼进 WHERE 子句。一旦你用字符串拼接构造 apply() 参数,比如 "status = '" + userInput + "'",攻击者输入 "1'; DROP TABLE user; --" 就会直接执行恶意语句。
安全写法:必须用 {0} 占位符绑定值
只有使用 {0}、{1} 等占位符形式,MyBatis-Plus 才会触发参数绑定机制,等价于 JDBC 的 PreparedStatement.setString(1, value),值被当作纯数据处理,不会参与 SQL 解析。
-
✅ 安全示例:
wrapper.apply("date_format(create_time, '%Y-%m-%d') = {0}", "2023-10-01") -
❌ 危险示例:
wrapper.apply("date_format(create_time, '%Y-%m-%d') = '" + dateStr + "'") - 多参数按顺序匹配:
wrapper.apply("price BETWEEN {0} AND {1}", minPrice, maxPrice) - 布尔条件控制是否生效:
wrapper.apply(hasDateFilter, "date(create_time) = {0}", dateStr)
字段名、表名、排序方向不能用 {0} 绑定
{0} 只对「值」有效,数据库结构部分(如列名、函数名、ORDER BY 方向、GROUP BY 字段)无法参数化。如果这些内容来自用户输入,必须走白名单校验。
- 错误做法:
wrapper.apply("ORDER BY {0} {1}", sortField, sortDirection)——{0}在这里会被忽略或报错,实际仍拼接字符串 - 正确做法:先校验
sortField是否在["name", "price", "create_time"]白名单中,再调用wrapper.orderByAsc(User::getName)或硬编码分支 - 同理,
apply("COUNT({0}) > 0", fieldName)是危险的,应改用inSql()或@Select显式写 SQL
LambdaQueryWrapper 也不能保你周全
用 LambdaQueryWrapper 写 eq(User::getStatus, input) 确实能防字段名注入,但它对 apply() 完全无感——只要你在里面拼字符串,它就不管。
-
lambdaWrapper.eq(User::getStatus, 1).apply("extra_info LIKE '%" + userInput + "%'")—— 前半段安全,后半段立刻破防 -
lambdaWrapper.apply("id IN (SELECT user_id FROM dept WHERE name = {0})", deptName)—— 这个才真正安全 - 别迷信“Lambda”前缀,关键看
apply()里怎么写
最常被忽略的一点:很多人以为只要用了 apply() 就得自己写 SQL,其实多数场景可以用 inSql()、like()、between() 或 func() 替代;真要上 apply(),就只让它干一件事——安全地塞值,其他都交给框架。











