CVE-2020-26945 mybatis二级缓存反序列化的分析与复现
字数 1575 2025-08-15 21:32:12
MyBatis二级缓存反序列化漏洞(CVE-2020-26945)分析与防御指南
1. MyBatis简介
MyBatis是一款优秀的持久层框架,最初是Apache的开源项目iBatis,2010年迁移到Google Code并更名为MyBatis。它支持:
- 定制化SQL
- 存储过程
- 高级映射
- 避免了几乎所有的JDBC代码
- 手动设置参数和获取结果集
- 使用简单XML或注解配置
2. 漏洞概述
CVE编号:CVE-2020-26945
发布日期:2020年10月6日
修复版本:MyBatis 3.5.6
漏洞类型:远程代码执行(RCE)
漏洞根源:二级缓存功能的反序列化安全问题
3. 漏洞利用条件
- 用户启用了MyBatis的二级缓存功能
- 攻击者能够修改缓存内容,替换为恶意反序列化数据
- 目标系统未设置JEP-290过滤机制
- 没有任何防御反序列化攻击的措施
4. 二级缓存机制详解
4.1 二级缓存工作原理
二级缓存将查询结果放入缓存中,下次查询相同条件时直接从缓存获取,降低SQL服务器压力。特点包括:
- 默认不开启,需要手动配置
- 可以缓存在Redis等KV数据库,也可自定义实现
- 被缓存的对象必须实现
Serializable接口
4.2 缓存接口定义
自定义缓存需要实现以下接口:
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
}
4.3 启用二级缓存
- MyBatis配置文件中开启:
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
- Mapper XML中加入缓存标签:
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"/>
5. 漏洞分析
5.1 问题根源
当使用支持序列化的缓存实现(如Redis缓存)时:
putObject方法会将对象序列化后存储getObject方法会从缓存中读取数据并反序列化- 如果攻击者能控制缓存内容,可注入恶意序列化数据
5.2 Redis缓存实现示例
官方Redis缓存插件的关键代码:
@Override
public void putObject(final Object key, final Object value) {
execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
final byte[] idBytes = id.getBytes();
jedis.hset(idBytes, key.toString().getBytes(),
redisConfig.getSerializer().serialize(value));
if (timeout != null && jedis.ttl(idBytes) == -1) {
jedis.expire(idBytes, timeout);
}
return null;
}
});
}
@Override
public Object getObject(final Object key) {
return execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
return redisConfig.getSerializer().unserialize(
jedis.hget(id.getBytes(), key.toString().getBytes()));
}
});
}
6. 漏洞复现步骤
6.1 环境搭建
- 使用示例项目:https://github.com/Lovelcp/spring-boot-mybatis-with-redis
- 修改ProductMapper.xml配置:
<mapper namespace="com.wooyoo.learning.dao.mapper.ProductMapper">
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"/>
<select id="select" resultType="Product">
SELECT * FROM products WHERE id = #{id} LIMIT 1
</select>
<update id="update" parameterType="Product" flushCache="true">
UPDATE products SET name = #{name}, price = #{price}
WHERE id = #{id} LIMIT 1
</update>
</mapper>
- 数据库配置(application.yml):
spring:
datasource:
url: jdbc:mysql://192.168.3.254/test?autoReconnect=true&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
6.2 攻击过程
- 访问正常端点(如
http://127.0.0.1:9999/product/1)确认二级缓存生效 - 使用Redis缓存插件时,替换缓存数据为恶意序列化对象
- 当系统从缓存读取并反序列化该对象时,触发RCE
7. 防御措施
7.1 风险评估
- 确认业务是否使用二级缓存
- 检查缓存服务器是否对外暴露
- 评估第三方缓存是否使用Java反序列化
7.2 具体防御方案
-
升级MyBatis:升级到3.5.6或更高版本
-
网络隔离:
- 确保缓存服务器不对外网开放
- 设置严格的网络访问控制
-
序列化防护:
- 实现自定义序列化器,增加安全校验
- 使用白名单机制限制可反序列化的类
-
JEP-290:启用JEP-290反序列化过滤器
-
缓存替代方案:
- 使用不依赖Java序列化的缓存实现
- 考虑使用JSON等安全格式存储缓存数据
-
最小权限原则:
- 缓存服务使用最小必要权限运行
- 数据库连接使用最小权限账户
8. 参考资源
- MyBatis官方修复PR:https://github.com/mybatis/mybatis-3/pull/2079
- 官方发布说明:https://blog.mybatis.org/
- JEP-290文档:https://openjdk.java.net/jeps/290
9. 总结
CVE-2020-26945漏洞的核心在于MyBatis二级缓存的反序列化机制可能被恶意利用。防御的关键在于:
- 控制缓存服务器的访问权限
- 避免不安全的反序列化操作
- 及时更新到安全版本
- 实施深度防御策略
通过合理配置和安全编码实践,可以充分利用MyBatis二级缓存的性能优势,同时避免安全风险。