溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何進(jìn)行FastJson反序列化RCE漏洞中TemplatesImpl的利用鏈分析

發(fā)布時間:2021-12-13 21:41:24 來源:億速云 閱讀:248 作者:柒染 欄目:網(wǎng)絡(luò)安全

這篇文章給大家介紹如何進(jìn)行FastJson反序列化RCE漏洞中TemplatesImpl的利用鏈分析,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

0. 前言

記錄在FastJson反序列化RCE漏洞分析和利用時的一些細(xì)節(jié)問題。

1. TemplatesImpl的利用鏈

關(guān)于 parse 和 parseObject

FastJson中的 parse() 和 parseObject()方法都可以用來將JSON字符串反序列化成Java對象,parseObject() 本質(zhì)上也是調(diào)用 parse() 進(jìn)行反序列化的。但是 parseObject() 會額外的將Java對象轉(zhuǎn)為 JSONObject對象,即 JSON.toJSON()。所以進(jìn)行反序列化時的細(xì)節(jié)區(qū)別在于,parse() 會識別并調(diào)用目標(biāo)類的 setter 方法及某些特定條件的 getter 方法,而 parseObject() 由于多執(zhí)行了 JSON.toJSON(obj),所以在處理過程中會調(diào)用反序列化目標(biāo)類的所有 setter 和 getter 方法。parseObject() 的源代碼如下:

public static JSONObject parseObject(String text) {
        Object obj = parse(text);
        if (obj instanceof JSONObject) {
            return (JSONObject) obj;
        }

        return (JSONObject) JSON.toJSON(obj);
}

舉個簡單的例子:

public class FastJsonTest {

    public String name;
    public String age;
    public FastJsonTest() throws IOException{
    }

    public void setName(String test) {
        System.out.println("name setter called");
        this.name = test;
    }

    public String getName() {
        System.out.println("name getter called");
        return this.name;
    }

    public String getAge(){
        System.out.println("age getter called");
        return this.age;
    }

    public static void main(String[] args) {
        Object obj = JSON.parse("{\"@type\":\"fastjsontest.FastJsonTest\",\"name\":\"thisisname\", \"age\":\"thisisage\"}");
        System.out.println(obj);

        Object obj2 = JSON.parseObject("{\"@type\":\"fastjsontest.FastJsonTest\",\"name\":\"thisisname\", \"age\":\"thisisage\"}");
        System.out.println(obj2);
    }

}

上述代碼運(yùn)行后可以看到,執(zhí)行parse() 時,只有 setName() 會被調(diào)用。執(zhí)行parseObject() 時,setName()、getAge()、getName() 均會被調(diào)用。

為什么會觸發(fā)getOutputProperties()

感覺上 parse() 進(jìn)行反序列化創(chuàng)建Java類應(yīng)該只會調(diào)用 setter 方法進(jìn)行成員變量賦值才對,會什么會觸發(fā)TemplatesImpl類中的 getOutputProperties() 方法呢?

另外 outputProperties 成員變量和 getOutputProperties() 明明差了一個``字符,是怎么被 FastJson 關(guān)聯(lián)上的?

如上一小節(jié)所述,parse() 進(jìn)行反序列化時其實(shí)會調(diào)用某些特定的 getter 方法進(jìn)行字段解析,而 TemplatesImpl類中的 getOutputProperties() 方法恰好滿足這一條件。

FastJson反序列化到Java類時主要邏輯如下:

  1. 獲取并保存目標(biāo)Java類中的成員變量、setter、getter。

  2. 解析JSON字符串,對字段逐個處理,調(diào)用相應(yīng)的setter、getter進(jìn)行變量賦值。

我們先看第一步,這里由 JavaBeanInfo.build() 進(jìn)行處理,F(xiàn)astJson會創(chuàng)建一個filedList數(shù)組,用來保存目標(biāo)Java類的成員變量以及相應(yīng)的setter或getter方法信息,供后續(xù)反序列化字段時調(diào)用。

filedList大致結(jié)構(gòu)如下:

[
    {
        name:"outputProperties",
        method:{
            clazz:{},
            name:"getOutputProperties",
            returnType:{},
            ...
        }
    }
]

FastJson并不是直接反射獲取目標(biāo)Java類的成員變量的,而是會對setter、getter、成員變量分別進(jìn)行處理,智能提取出成員變量信息。邏輯如下:

  1. 識別setter方法名,并根據(jù)setter方法名提取出成員變量名。如:識別出setAge()方法,F(xiàn)astJson會提取出age變量名并插入filedList數(shù)組。

  2. 通過clazz.getFields()獲取成員變量。

  3. 識別getter方法名,并根據(jù)getter方法名提取出成員變量名。

可以看到在 JavaBeanInfo.build() 中,有一段代碼會對getter方法進(jìn)行判斷,在某些特殊條件下,會從getter方法中提取出成員變量名并附加到filedList數(shù)組中。而TemplatesImpl類中的 getOutputProperties() 正好滿足這個特定條件。getter方法的處理代碼為:

JavaBeanInfo.java

public static JavaBeanInfo build(Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy) {
    ...
    for (Method method : clazz.getMethods()) { // getter methods
        String methodName = method.getName();
        if (methodName.length() < 4) {
            continue;
        }

        if (Modifier.isStatic(method.getModifiers())) {
            continue;
        }

        if (methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))) {
            if (method.getParameterTypes().length != 0) {
                continue;
            }

            // 關(guān)鍵條件

            if (Collection.class.isAssignableFrom(method.getReturnType()) //
                || Map.class.isAssignableFrom(method.getReturnType()) //
                || AtomicBoolean.class == method.getReturnType() //
                || AtomicInteger.class == method.getReturnType() //
                || AtomicLong.class == method.getReturnType() //
            ) {
                String propertyName;

                JSONField annotation = method.getAnnotation(JSONField.class);
                if (annotation != null && annotation.deserialize()) {
                    continue;
                }

                if (annotation != null && annotation.name().length() > 0) {
                    propertyName = annotation.name();
                } else {
                    propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
                }

                FieldInfo fieldInfo = getField(fieldList, propertyName);
                if (fieldInfo != null) {
                    continue;
                }

                if (propertyNamingStrategy != null) {
                    propertyName = propertyNamingStrategy.translate(propertyName);
                }

                add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, 0, 0, 0, annotation, null, null));
            }
        }
    }
    ...
}

接下來,F(xiàn)astJson會語義分析JSON字符串。根據(jù)字段key,調(diào)用filedList數(shù)組中存儲的相應(yīng)方法進(jìn)行變量初始化賦值。具體邏輯在 parseField() 中實(shí)現(xiàn):

JavaBeanDeserializer

public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType,
                              Map<String, Object> fieldValues) {
        JSONLexer lexer = parser.lexer; // xxx

        FieldDeserializer fieldDeserializer = smartMatch(key);

        ...

        return true;
    }

這里調(diào)用了一個神奇的 smartMatch() 方法,smartMatch()時會替換掉字段key中的_,從而 _outputProperties 和 getOutputProperties() 可以成功關(guān)聯(lián)上。

JavaBeanDeserializer

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;
                        }
                    }
                }
            }
        }

為什么需要對_bytecodes進(jìn)行Base64編碼

細(xì)心的你可以發(fā)現(xiàn),PoC中的 _bytecodes 字段是經(jīng)過Base64編碼的。為什么要這么做呢? 分析FastJson對JSON字符串的解析過程,原來FastJson提取byte[]數(shù)組字段值時會進(jìn)行Base64解碼,所以我們構(gòu)造payload時需要對 _bytecodes 進(jìn)行Base64處理。FastJson的處理代碼如下:

ObjectArrayCodec
    public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
        final JSONLexer lexer = parser.lexer;
        // ......省略部分代碼
        if (lexer.token() == JSONToken.LITERAL_STRING) {
            byte[] bytes = lexer.bytesValue();  // ... 在這里解析byte數(shù)組值
            lexer.nextToken(JSONToken.COMMA);
            return (T) bytes;
        }

// 接著調(diào)用JSONScanner.bytesValue()

JSONScanner
    public byte[] bytesValue() {
      return IOUtils.decodeBase64(text, np + 1, sp);
    }

關(guān)于如何進(jìn)行FastJson反序列化RCE漏洞中TemplatesImpl的利用鏈分析就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI