0x01 簡介
MyBatis 本是Apache的一個開源項目iBatis, 2010年這個項目由Apache Software Foundation 遷移到了Google Code,並且改名為MyBatis。MyBatis是一款優秀的持久層框架,它支持定制化SQL、存儲過程以及高級映射。MyBatis避免了幾乎所有的JDBC代碼和手動設置參數以及獲取結果集。MyBatis可以使用簡單的XML或注解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java對象)映射成數據庫中的記錄。
漏洞描述2020年10月6日,MyBatis官方發布了MyBatis 3.5.6版本,修復了一個遠程代碼執行漏洞,該漏洞編號為CVE-2020-26945。
利用條件
- 用戶啟用了二級緩存功能
- 攻擊者可以修改緩存的內容,替換為惡意反序列化數據
- 用戶未設置JEP-290過濾,且沒有任何防御反序列化攻擊的措施
0x02 分析
二級緩存其實就是將查詢的結果,放入緩存中,下次查詢相同的條件時,直接從緩存中獲取結果,降低sql服務器的壓力。如上圖所示,二級緩存可以緩存在redis等kv數據庫,也可以我們自己實現相關緩存。
如果我們需要自定義緩存,只需要集成如下接口即可
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();
}
二級緩存默認是不開啟的,需要手動開啟二級緩存,實現二級緩存的時候,MyBatis要求返回的POJO必須是可序列化的。開啟二級緩存的條件也是比較簡單,通過直接在 MyBatis 配置文件中通過
<settings>
<setting name = "cacheEnabled" value = "true" /></settings>
來開啟二級緩存,還需要在 Mapper 的xml 配置文件中加入 <cache>
標簽
二級緩存中,被緩存的對象必須是繼承自
Serializable
接口,緩存的過程其實就是將POJO反序列化后,存入緩存中。
0x03 復現
在網上隨便下載一個spring boot 二級緩存的學習項目,本地搭建就行。我用 https://github.com/Lovelcp/spring-boot-mybatis-with-redis 搭建
修改 ProductMapper.xml 配置文件如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<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>
然后在 org.apache.ibatis.cache.impl.PerpetualCache
相關位置打上斷點
設置好mysql數據庫,建庫建表,修改配置文件 如下圖所示
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
瀏覽器訪問
http://127.0.0.1:9999/product/1
我們可以看到,二級緩存生效了,將結果已經存儲到我們預先設定的緩存中,截圖如下
當然,key與value並沒有被序列化,因為系統默認的這個緩存,並不會序列化反序列化。
下面我們看一下mybatis 官方redis mybatis/redis-cache
緩存插件,相關操作
@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()));
}
});
}
在這里我們可以很明顯的看出,將對象反序列化后存儲至redis服務器中。
替換里面的數據為我們反序列化的攻擊內容即可。
0x04 防御措施
- 啟用的二級緩存沒有問題,問題在於,緩存服務器是否允許任意用戶訪問??
- 如果確認自己的業務沒有二級緩存或者二級緩存服務器對外不可見,可以暫時不用處理該漏洞
- 如果二級緩存服務器對外,重點檢查第三方緩存是否使用java反序列化
吐槽
某實驗室,你自己看看你翻譯的那玩意,看完一臉懵逼