mybatis-plus SQL 注入漏洞导致版本升级引发的问题
前言
最近做项目,对于企业级而言 JDK 和 Spring 的升级不能一蹴而就,而且 OpenJDK11 的支持时间不多了,升级到 JDK17 风险太大,所以缝缝补补,但是mybatis-plus 需要升级,这个就没办法了,其实这次漏洞影响的是 QueryWrapper 或者 UpdateWrapper 在动态 SQL 时通过调用方法com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils检查是否特殊 SQL 字符防注入。本身是日常升级,但是升级出事了。
准备 demo
POM
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.7.18</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.36</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><version>2.7.18</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.7</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.7</version></dependency></dependencies> 
随便写个 demo
@Mapper
public interface DemoDao {@Select("select * from user where name = #{name}")List<User> selectUser(String name);
}@RestController
public class DemoController {@Autowiredprivate DemoDao demoDao;@RequestMapping("/user")public List<User> listUser(String name){return demoDao.selectUser(name);}
}@SpringBootApplication
@MapperScan(basePackages = "com.boot.mybatis.plus.demo.dao")
public class MybatisPlusMain {public static void main(String[] args) {SpringApplication.run(MybatisPlusMain.class, args);}
} 
配置一个数据源即可
jar 冲突 1
启动上面是示例,发现失败了,原因是 mybatis-plus 的 3.5.7 和 mybatis 的 3.5.7 冲突,mybatis 3.5.7 较旧
2025-11-03 21:12:38.910 ERROR 1625 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : ***************************
APPLICATION FAILED TO START
***************************Description:An attempt was made to call a method that does not exist. The attempt was made from the following location:com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder$AnnotationWrapper.<init>(MybatisMapperAnnotationBuilder.java:653)The following method did not exist:org.apache.ibatis.annotations.Select.affectData()ZThe calling method's class, com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder$AnnotationWrapper, was loaded from the following location:jar:file:/Users/huahua/.m2/repository/com/baomidou/mybatis-plus-core/3.5.7/mybatis-plus-core-3.5.7.jar!/com/baomidou/mybatisplus/core/MybatisMapperAnnotationBuilder$AnnotationWrapper.classThe called method's class, org.apache.ibatis.annotations.Select, is available from the following locations:jar:file:/Users/huahua/.m2/repository/org/mybatis/mybatis/3.5.7/mybatis-3.5.7.jar!/org/apache/ibatis/annotations/Select.classThe called method's class hierarchy was loaded from the following locations:org.apache.ibatis.annotations.Select: file:/Users/huahua/.m2/repository/org/mybatis/mybatis/3.5.7/mybatis-3.5.7.jarAction:Correct the classpath of your application so that it contains compatible versions of the classes com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder$AnnotationWrapper and org.apache.ibatis.annotations.Select
 
查看源码发现

根据 maven 仓库分析

mybatis-plus 3.5.7 依赖 mybatis3.5.16

jar 冲突 2
升级 mybatis 版本到 3.5.16 后,如果同时使用 spring-data-jpa,那么 jsqlparser 就会冲突

本身而言只有 mybatis-plus 强依赖这个 jar,但是实际上 spring-data-jpa(除了 CN 基本上都是使用 jpa) 也隐形的依赖了这个 jar,且版本号与 mybatis-plus 不一样

但是是 optional,但是又是 provided 修饰

在org.springframework.data.jpa.repository.query.QueryEnhancerFactory中就会判断

发现有些类不存在,如果引入了 spring-data-jpa,不使用 jpa还好,如果使用则会 启动报错

解决办法有 2 个:
- mybatis-plus 排除 jsqlparser
 - 使用低版本的 jsqlparser,比如 3.1,既不能使用 mybatis-plus 依赖的高版本,也不能使用 spring-data-jpa 的 4.5 版本,不兼容
 
QueryWrapper IN 查询
@Mapper
public interface DemoDao {@Select("select * from User ${ew.customSqlSegment}")List<User> selectUser(@Param(Constants.WRAPPER) QueryWrapper<User> queryWrapper);
}@RequestMapping("/user")public List<User> listUser(String name){QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.in("name", Collections.emptySet());return demoDao.selectUser(queryWrapper);} 
适当改造代码,执行 http 访问
### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '))' at line 1
### The error may exist in com/boot/mybatis/plus/demo/dao/DemoDao.java (best guess)
### The error may involve com.boot.mybatis.plus.demo.dao.DemoDao.selectUser-Inline
### The error occurred while setting parameters
### SQL: select * from User WHERE (name IN ())
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '))' at line 1
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '))' at line 1] with root causejava.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '))' at line 1
 
SQL:select * from User WHERE (name IN ())
这个在 mybatis-plus 3.4 以下的版本可以正常运行,先看看 mybatis-plus 3.4 与之后的逻辑

其实 mybatis-plus 已经明确说明了,看看代码

看看 SQL 是怎么拼接的,因为是动态 SQL,所以每次动态拼接
org.apache.ibatis.parsing.GenericTokenParser

同理这里可以配置过滤器检查 SQL

看看 mybatis-plus 3.3.2 呢

其实为 null 会空指针,只有空集合元素才会不拼接

所以 mybatis-plus 3.4 以下不会出现问题,当然可能空指针
总结
mybatis-plus 真是一言难尽啊,国外基本上是使用 jpa,其实 mybatis 也有 jpa,mybatis-plus 和 spring-data-jpa 存在 jsqlparser 隐形冲突,mybatis-plus 和 mybatis 对相同 id 的处理不一样:https://blog.csdn.net/fenglllle/article/details/134912102。升级后 QueryWrapper IN处理空集合的逻辑不一样,这种需要全覆盖测试才行,否则特殊数据就......建议为了减少问题,只使用一样,比如 mybatis-plus 与 spring-data-jpa 二选一,另外尽量不使用 query wrapper 这样的动态 SQL 拼接,建议使用代码动态拼接。
