CVE-2020-26945 mybatis二級緩存反序列化的分析與復現


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。

利用條件

  1. 用戶啟用了二級緩存功能
  2. 攻擊者可以修改緩存的內容,替換為惡意反序列化數據
  3. 用戶未設置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 防御措施

  1. 啟用的二級緩存沒有問題,問題在於,緩存服務器是否允許任意用戶訪問??
  2. 如果確認自己的業務沒有二級緩存或者二級緩存服務器對外不可見,可以暫時不用處理該漏洞
  3. 如果二級緩存服務器對外,重點檢查第三方緩存是否使用java反序列化

吐槽

某實驗室,你自己看看你翻譯的那玩意,看完一臉懵逼

參考

  1. https://github.com/mybatis/mybatis-3/pull/2079


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM