Fastjson < 1.2.68版本反序列化漏洞分析篇
點(diǎn)擊上方藍(lán)色“程序猿DD”,選擇“設(shè)為星標(biāo)”
回復(fù)“資源”獲取獨(dú)家整理的學(xué)習(xí)資料!

前言
遲到的Fastjson反序列化漏洞分析,按照國(guó)際慣例這次依舊沒(méi)有放poc。道理還是那個(gè)道理,但利用方式多種多樣。除了之前放出來(lái)用于文件讀寫(xiě)的利用方式以外其實(shí)還可以用于SSRF。
?
一、漏洞概述
在之前其他大佬文章中,我們可以看到的利用方式為通過(guò)清空指定文件向指定文件寫(xiě)入指定內(nèi)容(用到第三方庫(kù))。當(dāng)gadget是繼承的第一個(gè)類(lèi)的子類(lèi)的時(shí)候,滿足攻擊fastjson的條件。此時(shí)尋找到的需要gadget滿足能利用期望類(lèi)繞過(guò)checkAutoType。
本文分析了一種利用反序列化指向fastjson自帶類(lèi)進(jìn)行攻擊利用,可實(shí)現(xiàn)文件讀取、SSRF攻擊等。
?
二、調(diào)試分析
1. 漏洞調(diào)試
從更新的補(bǔ)丁中可以看到expectClass類(lèi)新增了三個(gè)方法分別為:
java.lang.Runnable、java.lang.Readable、java.lang.AutoCloseable
首先,parseObject方法對(duì)傳入的數(shù)據(jù)進(jìn)行處理。通過(guò)詞法解析得到類(lèi)型名稱,如果不是數(shù)字則開(kāi)始checkAutoType檢查。
if (!allDigits) {
clazz = config.checkAutoType(typeName, null, lexer.getFeatures());
}
當(dāng)傳入的數(shù)據(jù)不是數(shù)字的時(shí)候,默認(rèn)設(shè)置期望類(lèi)為空,進(jìn)入checkAutoType進(jìn)行檢查傳入的類(lèi)。
final boolean expectClassFlag;
if (expectClass == null) {
expectClassFlag = false;
} else {
if (expectClass == Object.class
|| expectClass == Serializable.class
|| expectClass == Cloneable.class
|| expectClass == Closeable.class
|| expectClass == EventListener.class
|| expectClass == Iterable.class
|| expectClass == Collection.class
) {
expectClassFlag = false;
} else {
expectClassFlag = true;
}
}
判斷期望類(lèi),此時(shí)期望類(lèi)為null。往下走的代碼中,autoCloseable 滿足不在白名單內(nèi),不在黑名單內(nèi),autoTypeSupport沒(méi)有開(kāi)啟,expectClassFlag為false。
其中:
A.計(jì)算哈希值進(jìn)行內(nèi)部白名單匹配
B.計(jì)算哈希值進(jìn)行內(nèi)部黑名單匹配
C.非內(nèi)部白名單且開(kāi)啟autoTypeSupport或者是期望類(lèi)的,進(jìn)行hash校驗(yàn)白名單acceptHashCodes、黑名單denyHashCodes。如果在acceptHashCodes內(nèi)則進(jìn)行加載( defaultClassLoader),在黑名單內(nèi)則拋出 autoType is not support。
clazz = TypeUtils.getClassFromMapping(typeName);
滿足條件C后來(lái)到clazz的賦值,解析來(lái)的代碼中對(duì)clazz進(jìn)行了各種判斷
clazz = TypeUtils.getClassFromMapping(typeName);
從明文緩存中取出autoCloseable賦值給 clazz

clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz == null) {
clazz = deserializers.findClass(typeName);
}
if (clazz == null) {
clazz = typeMapping.get(typeName);
}
if (internalWhite) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
}
if (clazz != null) {
if (expectClass != null
&& clazz != java.util.HashMap.class
&& !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
當(dāng)clazz不為空時(shí),expectClassFlag為空不滿足條件,返回clazz,至此,第一次的checkAutoType檢查完畢。
ObjectDeserializer deserializer = config.getDeserializer(clazz);
Class deserClass = deserializer.getClass();
if (JavaBeanDeserializer.class.isAssignableFrom(deserClass)
&& deserClass != JavaBeanDeserializer.class
&& deserClass != ThrowableDeserializer.class) {
this.setResolveStatus(NONE);
} else if (deserializer instanceof MapDeserializer) {
this.setResolveStatus(NONE);
}
Object obj = deserializer.deserialze(this, clazz, fieldName);
return obj;
將檢查完畢的autoCloseable進(jìn)行反序列化,該類(lèi)使用的是JavaBeanDeserializer反序列化器,從MapDeserializer中繼承
public
T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
return deserialze(parser, type, fieldName, 0);
}
publicT deserialze(DefaultJSONParser parser, Type type, Object fieldName, int features) {
return deserialze(parser, type, fieldName, null, features, null);
}//進(jìn)入后代碼如下
if ((typeKey != null && typeKey.equals(key))
|| JSON.DEFAULT_TYPE_KEY == key) {
lexer.nextTokenWithColon(JSONToken.LITERAL_STRING);
if (lexer.token() == JSONToken.LITERAL_STRING) {
String typeName = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);
if (typeName.equals(beanInfo.typeName)|| parser.isEnabled(Feature.IgnoreAutoType)) {
// beanInfo.typeName是autoCloseable ,但I(xiàn)gnoreAutoType沒(méi)有開(kāi)啟
if (lexer.token() == JSONToken.RBRACE) {
lexer.nextToken();
break;
}
continue;
}//不滿足條件所以這塊代碼被跳過(guò)了
JSON.DEFAULT_TYPE_KEY 為@type?,并給它賦值傳入的key?@type?,將第二個(gè)類(lèi)也就是這次 的gadget傳入
if (deserializer == null) {
Class> expectClass = TypeUtils.getClass(type);
userType = config.checkAutoType(typeName, expectClass, lexer.getFeatures());
deserializer = parser.getConfig().getDeserializer(userType);
}
期望類(lèi)在這里發(fā)生了變化,expectClass的值變?yōu)閖ava.lang.AutoCloseable,typeName為gadget,
boolean jsonType = false;
InputStream is = null;
try {
String resource = typeName.replace('.', '/') + ".class";
if (defaultClassLoader != null) {
is = defaultClassLoader.getResourceAsStream(resource);
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
//開(kāi)了一個(gè)class文件的輸入流
}
if (is != null) {
ClassReader classReader = new ClassReader(is, true);//new reader工具
TypeCollector visitor = new TypeCollector("" , new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
}
} catch (Exception e) {
// skip
} finally {
IOUtils.close(is);//關(guān)閉流 JarURLConnection$JarURLInputStream
}
來(lái)到JSONType注解,取typename gadget轉(zhuǎn)換變?yōu)槁窂剑瑀esource通過(guò)將 “.” 替換為”/“得到路徑 。其實(shí)已經(jīng)開(kāi)始讀取gadget了,它本意應(yīng)該是加載AutoCloseable。
public ClassReader(InputStream is, boolean readAnnotations) throws IOException {
this.readAnnotations = readAnnotations;
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
for (; ; ) {
int len = is.read(buf);
if (len == -1) {
break;
}
if (len > 0) {
out.write(buf, 0, len);
}
}
is.close();
this.b = out.toByteArray();
}
可以看到這里有讀取文件的功能。所以之前網(wǎng)傳的POC可能是利用這里這個(gè)特性(?)留意一下以后研究…
if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
//開(kāi)始加載gadget
}
if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {//判斷里面的類(lèi)是否為繼承類(lèi)
TypeUtils.addMapping(typeName, clazz);
return clazz;
} else {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}
isAssignableFrom()這個(gè)方法用于判斷里面的類(lèi)是否為繼承類(lèi),當(dāng)利用了java.lang.AutoCloseable這個(gè)方法去攻擊fastjson,那么后續(xù)反序列化的鏈路需要是繼承于該類(lèi)的子類(lèi)。
TypeUtils.addMapping(typeName, clazz)這一步成功把gadget加入緩存中并返回被賦值gadget的clazz.

checkAutoType正式檢查完畢,此時(shí)用deserializer = parser.getConfig().getDeserializer(userType); userType既gadget進(jìn)行反序列化。
private void xxTryOnly(boolean isXXXXeconnect, Properties mergedProps) throws
XXXException {
Exception connectionNotEstablishedBecause = null;
try {
coreConnect(mergedProps);
this.connectionId = this.io.getThreadId();
this.isClosed = false;
進(jìn)入coreConnect()


在這里進(jìn)行連接。至此漏洞利用完結(jié)。
?
2. 總結(jié)
在本次反序列化漏洞中,筆者認(rèn)為關(guān)鍵點(diǎn)在于找到合適并且可利用的常用jar包中的gadget。gadget在被反序列化后即可執(zhí)行類(lèi)里的惡意的功能(不僅限于RCE還包括任意文件讀取/創(chuàng)建,SSRF等)。也可以使本漏洞得到最大化的利用。
?
三、參考考鏈接
https://b1ue.cn/archives/348.html
https://daybr4ak.github.io/2020/07/20/fastjson%201.6.68%20autotype%20bypass/
往期推薦
掃一掃,關(guān)注我
一起學(xué)習(xí),一起進(jìn)步
每周贈(zèng)書(shū),福利不斷
﹀
﹀
﹀
深度內(nèi)容
推薦加入
最近熱門(mén)內(nèi)容回顧? ?#技術(shù)人系列

