Fastjson系列
Fastjson
Fastjson简介
Fastjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作。
项目地址:https://github.com/alibaba/fastjson
演示代码demo
首先在pom.xml添加依赖
1 |
|
Student.java
1 |
|
序列化函数:JSON.toJsonString()
反序列化函数:JSON.parseObject/JSON.parse
序列化demo
StudentSerialize.java
1 |
|
SerializerFeature.WriteClassName,是JSON.toJSONString()中的一个设置属性值,设置之后在序列化的时候会多写入一个@type,即写上被序列化的类名,type可以指定反序列化的类,并且调用其getter/setter/is方法。
Fastjson接受的JSON可以通过@type字段来指定该JSON应当还原成何种类型的对象,在反序列化的时候方便操作。
输出如下
1 |
|
我们可以跟一下序列化部分。直接跟进JSON.toJSONString函数,因为是对Student对象序列化,所以序列化对的逻辑应该就是在serializer.write(object)里面了。
反序列化demo
1 |
|
输出如下
1 |
|
反序列化时的 Feature.SupportNonPublicField 参数
我们把setAge函数注释掉,age作为一个私有变量,如果我们反序列化时,一个变量是私有的,并且没有对应的setter函数,那么反序列化的时候就会返回null,不会真正实现这个变量,这时候就需要用到SupportNonPublicField 参数了,这样我们的私有变量就可以正常反序列化了。
由于该字段在fastjson1.2.22版本引入,所以只能影响1.2.22-1.2.24
未使用SupportNonPublicField
1 |
|
输出如下
1 |
|
使用SupportNonPublicField
1 |
|
输出如下
1 |
|
可以看到现在我们的age成功反序列化出来了。
JSON.parseObject不指定Object.class
调整student类如下,所有私有变量不设置setter函数
1 |
|
1 |
|
输出如下
1 |
|
从输出结果来看,我们调用了Student类的构造函数,以及setter方法,和getter方法,但无论定义的对象是Object还是JSONObject,最后反序列化得到的都是JSONObject类对象,可以看到是未反序列化成功的。
我们来为JSON.parseObject指定反序列化类型(设置第二个参数),为Object.class或Student.class
1 |
|
输出如下
1 |
|
可以看到这次反序列化成功,获取到了Student这个类,我们为JSON.parseObject
指定反序列化类型(设置第二个参数),那么通过@type
的作用,会将JSON转换成对应的类。如果没有@type
,我们也可以通过第二个参数来控制需要转换成的类。可以参考下面代码,输出和上面一样。JSON.parseObject
如果不指定反序列化类型(设置第二个参数),则会返回fastjson.JSONObject
,无法将传入的类进行转换
1 |
|
一些结论
感觉自己说不出什么花来,还是在前辈的文章上学习,索性直接引用 Mi1k7ea 师傅的结论
根据前面的结果,有如下结论:
- 当反序列化为
JSON.parseObject(*)
形式即未指定class时,会调用反序列化得到的类的构造函数、所有属性的getter方法、JSON里面的非私有属性的setter方法,其中properties属性的getter方法调用了两次; - 当反序列化为
JSON.parseObject(*,*.class)
形式即指定class时,只调用反序列化得到的类的构造函数、JSON里面的非私有属性的setter方法、properties属性的getter方法; - 当反序列化为
JSON.parseObject(*)
形式即未指定class进行反序列化时得到的都是JSONObject类对象,而只要指定了class即JSON.parseObject(*,*.class)
形式得到的都是特定的Student类;
Fastjson会对满足下列要求的setter/getter方法进行调用:
满足条件的setter:
- 函数名长度大于4且以set开头
- 非静态函数
- 返回类型为void或当前类
- 参数个数为1个
满足条件的getter:
- 函数名长度大于等于4
- 非静态方法
- 以get开头且第4个字母为大写
- 无参数
- 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong
parse与parseObject区别
还记的前面提到的反序列化方法吗,JSON.parseObject/JSON.parse,我们浅浅研究了下JSON.parseObject,那么JSON.parse和它比有什么区别呢?
**
parse()
**:返回的是
JSONObject
类型。它会将 JSON 字符串解析为JSONObject
,适用于在没有特定类定义的情况下处理 JSON 数据。适用于在没有特定类定义的情况下,需要动态处理 JSON 数据时。返回的是实际类型的对象。根据输入的 JSON 字符串和传入的类类型,它会返回一个对应的 Java 对象。适用于当你有明确的类定义并希望直接得到该类的实例时。
适用于当你有明确的类定义并希望直接得到该类的实例时。
示例:
1
2String jsonString = "{\"name\":\"John\",\"age\":30}";
Person person = JSON.parseObject(jsonString, Person.class);在这种情况下,
Person
类中的 setter 方法会被调用,直接返回Person
类的实例。
**
parseObject()
**:适用于在没有特定类定义的情况下,需要动态处理 JSON 数据时。
示例:
1
2String jsonString = "{\"name\":\"John\",\"age\":30}";
JSONObject jsonObject = JSON.parseObject(jsonString);这会返回一个
JSONObject
,可以通过jsonObject.getString("name")
和jsonObject.getIntValue("age")
来访问数据。
也就是说,我们用parse()反序列化会直接得到特定的类,而无需像parseObject()一样返回的是JSONObject类型的对象、还可能需要去设置第二个参数指定返回特定的类。
1 |
|
输出如下
1 |
|
Fastjson反序列化漏洞原理
由前面的demo可以看出FastJson自己实现了一套序列化和反序列化的方法设计,没有使用的java原生序列化反序列化方法,大致就是一个对象序列化成一个Json格式的字符串,反序列化则是由Json格式字符串变成一个类。
如果我们可以传入一个恶意构造的JSON内容,程序对其进行反序列化后得到恶意类并执行了恶意类中的恶意函数,那么就可以导致代码执行。
那么如何才能够反序列化出恶意类呢?
还记得之前提到的吗,我们可以为JSON.parseObject
指定反序列化类型(设置第二个参数),如果我们设置的是Object.class,那么就可以反序列化任意类,因为我们设置的是Object.class,那么我们可以根据传入的JSON字符串中的@type来反序列化出想要的类,除了Object.class,还有JSON.class也可以反序列化任意类,另外JSONObject.class不能反序列化出想要的类,但是会去调用@type指定类的public属性的setter方法和getProperties方法,如果恶意代码存在setter方法或者getProperties方法也可以造成命令执行。
由前面知道,在某些情况下进行反序列化时会将反序列化得到的类的构造函数、getter方法、setter方法执行一遍,如果这三种方法中存在危险操作,则可能导致反序列化漏洞的存在。换句话说,就是攻击者传入要进行反序列化的类中的构造函数、getter方法、setter方法中要存在漏洞才能触发。
小结
若反序列化指定类型的类如Student obj = JSON.parseObject(jsonstring, Student.class);
,该类本身的构造函数、setter方法、Properties属性的getter方法存在危险操作,则存在Fastjson反序列化漏洞;
若反序列化未指定类型的类如Object obj = JSON.parseObject(jsonstring, Object.class);
,该若该类的子类的构造方法、setter方法、Properties属性的getter方法存在危险操作,则存在Fastjson反序列化漏洞;
若反序列化未指定类型的类如Object obj = JSON.parseObject(jsonstring);
,该若该类的子类的构造方法、setter方法、getter方法存在危险操作,则存在Fastjson反序列化漏洞;
1.2.22-1.2.24反序列化漏洞
环境
导入pom.xml依赖
1 |
|
对于Fastjson 1.2.22-1.2.24 版本的反序列化漏洞的利用,目前已知的主要有以下的利用链:
- 基于TemplateImpl;
- 基于JNDI(又分为基于Bean Property类型和Field类型);
TemplatesImpl利用链
TemplatesImpl在我们学习反序列化基础的时候CC2、CC3学习到过。
我们的恶意类需要继承AbstractTranslet接口,后面会分析为什么需要这样。
EvilCode.java
1 |
|
TemplatesImpl_POC.java
1 |
|
我们的payload如下
1 |
|
Payload中几个重要的Json键的含义:
- @type——指定的解析类,即
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
,Fastjson根据指定类去反序列化得到该类的实例,在默认情况下只会去反序列化public修饰的属性,在Payload中,_bytecodes
和_name
都是私有属性,所以想要反序列化这两个属性,需要在parseObject()
时设置Feature.SupportNonPublicField
; - _bytecodes——是我们把恶意类的.class文件二进制格式进行Base64编码后得到的字符串;
- _outputProperties——漏洞利用链的关键会调用其参数的getOutputProperties()方法,进而导致命令执行;
- **_tfactory:{}**——在defineTransletClasses()时会调用getExternalExtensionsMap(),当为null时会报错,所以要对_tfactory设置;
TemplatesImpl调用链
1 |
|
TemplatesImpl利用链思路
构造一个TemplatesImpl反序列化的字符串,其中_bytecodes设置为我们恶意的代码的字节码,接着,我们会反射去调用newInstance函数来实例化这个恶意类,而在实例化的过程中,触发我们的恶意类的构造函数,触发命令执行。
而想要达到上面的目的,根据我们之前提到的利用链,需要调用到TemplatesImpl#newTransformer。并且,我们是要在反序列化的过程中自动触发TemplatesImpl#newTransformer函数,我们可以考虑的是之前的结论
若反序列化指定类型的类如
Student obj = JSON.parseObject(jsonstring, Student.class);
,该类本身的构造函数、setter方法、Properties属性的getter方法存在危险操作,则存在Fastjson反序列化漏洞;若反序列化未指定类型的类如
Object obj = JSON.parseObject(jsonstring, Object.class);
,该若该类的子类的构造方法、setter方法、Properties属性的getter方法存在危险操作,则存在Fastjson反序列化漏洞;若反序列化未指定类型的类如
Object obj = JSON.parseObject(jsonstring);
,该若该类的子类的构造方法、setter方法、getter方法存在危险操作,则存在Fastjson反序列化漏洞;
我们需要找到一条链,链首是上面提到的比较危险的方法,比如setter方法、Properties属性的getter方法,而这条链最终调用到TemplatesImpl#newTransformer函数,其实往上跟一步就找到链首了,getOutputProperties这个getter方法,正好是Properties属性,所以这个函数正是我们想要的。
那这样我们就可以构造POC了,JSON格式如下
1 |
|
调试分析
接下来的调试还是比较繁琐的,记录的目的也是为了自己更有印象,也是为了调试的时候更细心一点,避免忽略了一些细节。读者可以跳过此部分。
在序列化的函数代码打上断点,我们调试过程这种重点关注输入的流转。
1 |
|
我们的输入(恶意payload)在input里面,进入了DefaultJSONParser类的构造函数。创建了一个默认的JSON解析器
这个构造函数,如果你的输入是{xxx}的形势,就会给你的lexer.token赋值JSONToken.LBRACE这个常量,也就是12,相当于做了个标准,表示是”{“这个符号开头的字符串
接着在339行进入了这个默认JSON解析器的parseObject方法。这个名字一看就是解析对象
1 |
|
这个函数中,我们会去根据将type也就是parseObject(clazz, null);中的clazz作为参数传入getDeserialize函数,大概意思是得到反序列化(反序列化器),
跟进去之后,第一行代码会通过IdentityHashMap.java#get方法获取type对应的derializer,然后进入第一个if直接返回这个derializer,数据类型为JavaObjectDeserializer。
1 |
|
接着就调用这个derializer(反序列化器)的deserialze方法,也就是JavaObjectDeserializer#derializer,这个函数很简单。其实就执行一行代码,在第45行。
1 |
|
执行了我们之前创建的默认JSON解析器parser的parse(解析)方法,在一个switch方法里面,根据token值进行不同的case操作,之前我们给lexer.token赋值了JSONToken.LBRACE,也就是12,然后就会进入case LBRACE。然后new一个JSONObject对象,然后执行DefaultJSONParser#parseObject方法解析这个对象,fieldName为null。
这里会对我们的JSON字符串进行一些检测,比如当前字符串是否是null,是否是}
,是否既不是{
,也不是,
,这些判断会抛出异常或者不继续执行代码逻辑,而我们当前是{
字符,正常进入函数逻辑,接着会检查下一个字符是否是"
,我们显示符合,然后就会获取我们第一个键值对的键名,也就是@type
,赋值给key。
然后我们会进入一个if判断,判断key是否是@type,和是否开启Feature.DisableSpecialKeyDetect选项,默认没有开启,所以这个if可以直接进入,然后获取key对应的值,也就是@type的值com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
,然后加载这个类。
函数里面会加载TemplatesImpl,然后吧加载的Class对象clazz和TemplatesImpl类名字添加到一个Map里面,然后return加载的clazz。
1 |
|
接着继续跟,来到下面两句,会先获取clazz对应的deserializer(反序列化器),然后执行这个deserializer的deserialze函数。
由于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
的deserializer没有存在IdentityHashMap里面,无法直接获取,进入第二个if
1 |
|
进入ParserConfig#getDeserializer,里面有检测黑名单,然后进去一系列if判断这个类的位置,没有找到。
最后到ParserConfig#createJavaBeanDeserializer函数,type和clazz一样。
在里面调用了JavaBeanInfo#build。
JavaBeanInfo#build函数中,第328行的for循环,会循环methods,而methods在135行赋值Method[] methods = clazz.getMethods();
,所以methods是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
中的所有方法。
然后会有一些if判断,挑选中合适的方法,在429行的add函数中添加到fieldList中。这里主要是筛选setter方法。
1 |
|
在490行又是一个类似的for循环,这一部分和上面差不多,筛选符合条件的getter方法放入fieldList。
然后将初始化一个JavaBeanInfo存储这些信息,然后返回给beanInfo。
接着一些if判断都是false,直到586行才为true,进入if里面JavaBeanDeserializer函数
会调用重写的JavaBeanDeserializer方法,而第二个参数调用build,其实和我们之前的build结果一样。相当于吧config和之前的JavaBeanInfo作为参数执行了JavaBeanDeserializer方法。
遍历beaninfo
中的sortedFields
,然后根据对应的属性创建对应的Deserializer(反序列化器),然后添加到sortedFieldDeserializers
中
在第二个for循环中会根据fieldInfo
的名字调用getFieldDeserializer
函数在sortedFieldDeserializers
数组中寻找对应的Deserializer(反序列化器)。
回到getDeserializer
函数,返回反序列化器。
回到DefaultJSONParser#parseObject
函数,将反序列化器赋值给deserializer,数据类型为JavaBeanDeserializer,然后调用它的JavaBeanDeserializer#deserialze方法,开始正式反序列化。
前面做了一些检查,到570行。函数名字意思是创建一个实例化,跟进。
然后经过if判断来到这里,获取了TemplatesImpl的默认构造函数,然后通过newInstance创建类的实例。此时我们就获取了TemplatesImpl类。
接着来到parseField函数,大概意思是解析字段。
JavaBeanDeserializer#parseField调用JavaBeanDeserializer#smartMatch来匹配key对应的字段反序列化器。而key匹配的对象就是之前在JavaBeanInfo#build函数中符合条件的setter和getter函数的字段名。有三个outputProperties
,stylesheetDOM
,uRIResolver
。
接着这里会有一些替换操作,比如我们传入的键名为_bytecodes,会替换掉,变为bytecodes。
然后又调用getFieldDeserializer函数获取key对应的字段反序列化器,而我们知道,只有_outputProperties这个参数存在对应的字段反序列化器。所以当key为_bytecodes匹配不到,返回null。
然后回到JavaBeanDeserializer#parseField,虽然返回null,但是后面会在ConcurrentHashMap中在获取key对应的deserOrField。然后获取其对应的DefaultFieldDeserializer(默认字段反序列化器)
然后调用这个反序列化器的parseField解析字段操作。fieldValueDeserilizer为null,进入getFieldValueDeserilizer给fieldValueDeserilizer赋值
接着会去用fieldValueDeserilizer的deserialze反序列化操作对_bytecodes反序列化,然后在83行setValue中给TemplatesImpl对象赋值。
可以看一下调用堆栈。这里进入了decodeBase64函数中,对我们的_bytecodes对应的值进行base64解码,然后就可以获取到我们恶意代码的字节流了,然后会在setValue赋值给TemplatesImpl对象。这也是为什么我们的payload需要base64编码的原因。
后面的几个键对应的值也是用这种方式赋值,只是不需要base64解码了。因为他们需要赋值的对象不是byte[]数组类型。具体可以自己调试,注意在ObjectArrayCodec#deserialze绕一圈进入第二次才会进去解析_bytecodes的部分
对于key等于_outputProperties时,我们是可以直接获取它的字段反序列化器的。当它进入SetValue函数的时候,是会通过反射调用getoutputProperties方法。然后就会走到我么TemplatesImpl利用链的链首了。
调用堆栈
1 |
|
一些问题总结
恶意类为什么要继承AbstractTranslet
类?
直接跟,调用堆栈如下
1 |
|
只有当要加载的类,父类等于com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
时,才能有_transletIndex = i
这样才能使得我们的(AbstractTranslet) _class[_transletIndex].newInstance()
语句能正常执行。而这条语句,才是正常触发命令的语句,因为加载类之后还需要实例化,这一句刚好是对我们加载的恶意类进行实例化。
为什么需要对_bytecodes进行Base64编码
调试过程中已经说明。对_bytecodes赋值前会进行base64解码。调试堆栈如下
为什么需要设置_tfactory为{}
因为在TemplatesImpl#defineTransletClasses中会调用_tfactory的一个方法,如果为_tfactory为null会报错。
那它的值应该为TransformerFactoryImpl对象,为什么我们传入的是{}
JavaBeanDeserializer#deserialze中,如果_tfactory值为空,会自动创建对应的实例对象,然后返回创建的这个实例对象。
JdbcRowSetImpl的利用链
基于JdbcRowSetImpl的利用链主要有两种利用方式,即JNDI+RMI和JNDI+LDAP,都是属于基于Bean Property类型的JNDI的利用方式。
JNDI + RMI
1 |
|
1 |
|
JNDI+LDAP
1 |
|
1 |
|
两种方式大差不差,主要是针对不同版本jdk,因为这是基于JNDI注入的攻击。
调试分析
并不复杂,前面和TemplatesImpl链差不多,只是中间有段临时代码。
断点直接打到com.sun.rowset.JdbcRowSetImpl
的setAutoCommit和setDataSourceName两个函数,我们的payload设置了dataSourceName和autoCommit值,因此会反序列化会调用setAutoCommit和setDataSourceName。
先进入setAutoCommit,调用了父类的setAutoCommit,参数是我们设置的恶意服务地址。
然后dataSoutce设置为恶意服务地址。
然后进入到setAutoCommit,调用JdbcRowSetImpl#connect方法
然后发现调用了熟悉的lookup,参数是恶意服务地址,接下就是熟悉的JNDI注入了。
fastjson修复手段
我们先看相比于fastjson1.2.24,作出了什么改变。
将DefaultJSONParser.parseObject()函数中的TypeUtils.loadClass
替换为checkAutoType()函数
进入checkAutoType函数,大致就是黑白名单对反序列化的类型过滤,如果开启了autoTypeSupport,就先白名单,再黑名单,否则就先黑名单,再白名单。默认情况下,autoTypeSupport为False,即先进行黑名单过滤,遍历denyList,然后遍历acceptList
白名单(acceptList)默认为空,黑名单(denyList)有如下类型。
1 |
|
autoTypeSupport
关于autoTypeSupport的知识,我直接引用drunkbaby师傅的。
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
利用链
下面内容来自LeadroyaL/fastjson-blacklist (github.com),可以直接github上看。
fastjson
在1.2.42开始,把原本明文的黑名单改成了哈希过的黑名单,防止安全研究者对其进行研究。
fastjson
在1.2.61开始,在https://github.com/alibaba/fastjson/commit/d1c0dff9a33d49e6e7b98a4063da01bbc9325a38中,把黑名单从十进制数变成了十六进制数,可能是为了防止安全研究者进行搜索。
目前的列表
version | hash | hex-hash | name |
---|---|---|---|
1.2.42 | -8720046426850100497 | 0x86fc2bf9beaf7aefL | org.apache.commons.collections4.comparators |
1.2.42 | -8109300701639721088 | 0x8f75f9fa0df03f80L | org.python.core |
1.2.42 | -7966123100503199569 | 0x9172a53f157930afL | org.apache.tomcat |
1.2.42 | -7766605818834748097 | 0x9437792831df7d3fL | org.apache.xalan |
1.2.42 | -6835437086156813536 | 0xa123a62f93178b20L | javax.xml |
1.2.42 | -4837536971810737970 | 0xbcdd9dc12766f0ceL | org.springframework. |
1.2.42 | -4082057040235125754 | 0xc7599ebfe3e72406L | org.apache.commons.beanutils |
1.2.42 | -2364987994247679115 | 0xdf2ddff310cdb375L | org.apache.commons.collections.Transformer |
1.2.42 | -1872417015366588117 | 0xe603d6a51fad692bL | org.codehaus.groovy.runtime |
1.2.42 | -254670111376247151 | 0xfc773ae20c827691L | java.lang.Thread |
1.2.42 | -190281065685395680 | 0xfd5bfc610056d720L | javax.net. |
1.2.42 | 313864100207897507 | 0x45b11bc78a3aba3L | com.mchange |
1.2.42 | 1203232727967308606 | 0x10b2bdca849d9b3eL | org.apache.wicket.util |
1.2.42 | 1502845958873959152 | 0x14db2e6fead04af0L | java.util.jar. |
1.2.42 | 3547627781654598988 | 0x313bb4abd8d4554cL | org.mozilla.javascript |
1.2.42 | 3730752432285826863 | 0x33c64b921f523f2fL | java.rmi |
1.2.42 | 3794316665763266033 | 0x34a81ee78429fdf1L | java.util.prefs. |
1.2.42 | 4147696707147271408 | 0x398f942e01920cf0L | com.sun. |
1.2.42 | 5347909877633654828 | 0x4a3797b30328202cL | java.util.logging. |
1.2.42 | 5450448828334921485 | 0x4ba3e254e758d70dL | org.apache.bcel |
1.2.42 | 5751393439502795295 | 0x4fd10ddc6d13821fL | java.net.Socket |
1.2.42 | 5944107969236155580 | 0x527db6b46ce3bcbcL | org.apache.commons.fileupload |
1.2.42 | 6742705432718011780 | 0x5d92e6ddde40ed84L | org.jboss |
1.2.42 | 7179336928365889465 | 0x63a220e60a17c7b9L | org.hibernate |
1.2.42 | 7442624256860549330 | 0x6749835432e0f0d2L | org.apache.commons.collections.functors |
1.2.42 | 8838294710098435315 | 0x7aa7ee3627a19cf3L | org.apache.myfaces.context.servlet |
1.2.43 | -2262244760619952081 | 0xe09ae4604842582fL | java.net.URL |
1.2.46 | -8165637398350707645 | 0x8eadd40cb2a94443L | junit. |
1.2.46 | -8083514888460375884 | 0x8fd1960988bce8b4L | org.apache.ibatis.datasource |
1.2.46 | -7921218830998286408 | 0x92122d710e364fb8L | org.osjava.sj. |
1.2.46 | -7768608037458185275 | 0x94305c26580f73c5L | org.apache.log4j. |
1.2.46 | -6179589609550493385 | 0xaa3daffdb10c4937L | org.logicalcobwebs. |
1.2.46 | -5194641081268104286 | 0xb7e8ed757f5d13a2L | org.apache.logging. |
1.2.46 | -3935185854875733362 | 0xc963695082fd728eL | org.apache.commons.dbcp |
1.2.46 | -2753427844400776271 | 0xd9c9dbf6bbd27bb1L | com.ibatis.sqlmap.engine.datasource |
1.2.46 | -1589194880214235129 | 0xe9f20bad25f60807L | org.jdom. |
1.2.46 | 1073634739308289776 | 0xee6511b66fd5ef0L | org.slf4j. |
1.2.46 | 5688200883751798389 | 0x4ef08c90ff16c675L | javassist. |
1.2.46 | 7017492163108594270 | 0x616323f12c2ce25eL | oracle.net |
1.2.46 | 8389032537095247355 | 0x746bd4a53ec195fbL | org.jaxen. |
1.2.48 | 1459860845934817624 | 0x144277b467723158L | java.net.InetAddress |
1.2.48 | 8409640769019589119 | 0x74b50bb9260e31ffL | java.lang.Class |
1.2.49 | 4904007817188630457 | 0x440e89208f445fb9L | com.alibaba.fastjson.annotation |
1.2.59 | 5100336081510080343 | 0x46c808a4b5841f57L | org.apache.cxf.jaxrs.provider. |
1.2.59 | 6456855723474196908 | 0x599b5c1213a099acL | ch.qos.logback. |
1.2.59 | 8537233257283452655 | 0x767a586a5107feefL | net.sf.ehcache.transaction.manager. |
1.2.60 | 3688179072722109200 | 0x332f0b5369a18310L | com.zaxxer.hikari. |
1.2.61 | -4401390804044377335 | 0xc2eb1e621f439309L | flex.messaging.util.concurrent.AsynchBeansWorkManagerExecutor |
1.2.61 | -1650485814983027158 | 0xe9184be55b1d962aL | org.apache.openjpa.ee. |
1.2.61 | -1251419154176620831 | 0xeea210e8da2ec6e1L | oracle.jdbc.rowset.OracleJDBCRowSet |
1.2.61 | -9822483067882491 | 0xffdd1a80f1ed3405L | com.mysql.cj.jdbc.admin. |
1.2.61 | 99147092142056280 | 0x1603dc147a3e358L | oracle.jdbc.connector.OracleManagedConnectionFactory |
1.2.61 | 3114862868117605599 | 0x2b3a37467a344cdfL | org.apache.ibatis.parsing. |
1.2.61 | 4814658433570175913 | 0x42d11a560fc9fba9L | org.apache.axis2.jaxws.spi.handler. |
1.2.61 | 6511035576063254270 | 0x5a5bd85c072e5efeL | jodd.db.connection. |
1.2.61 | 8925522461579647174 | 0x7bddd363ad3998c6L | org.apache.commons.configuration.JNDIConfiguration |
1.2.62 | -9164606388214699518 | 0x80d0c70bcc2fea02L | org.apache.ibatis.executor. |
1.2.62 | -8649961213709896794 | 0x87f52a1b07ea33a6L | net.sf.cglib. |
1.2.62 | -6316154655839304624 | 0xa85882ce1044c450L | oracle.net. |
1.2.62 | -5764804792063216819 | 0xafff4c95b99a334dL | com.mysql.cj.jdbc.MysqlDataSource |
1.2.62 | -4608341446948126581 | 0xc00be1debaf2808bL | jdk.internal. |
1.2.62 | -4438775680185074100 | 0xc2664d0958ecfe4cL | aj.org.objectweb.asm. |
1.2.62 | -3319207949486691020 | 0xd1efcdf4b3316d34L | oracle.jdbc. |
1.2.62 | -2192804397019347313 | 0xe1919804d5bf468fL | org.apache.commons.collections.comparators. |
1.2.62 | -2095516571388852610 | 0xe2eb3ac7e56c467eL | net.sf.ehcache.hibernate. |
1.2.62 | 4750336058574309 | 0x10e067cd55c5e5L | com.mysql.cj.log. |
1.2.62 | 218512992947536312 | 0x3085068cb7201b8L | org.h2.jdbcx. |
1.2.62 | 823641066473609950 | 0xb6e292fa5955adeL | org.apache.commons.logging. |
1.2.62 | 1534439610567445754 | 0x154b6cb22d294cfaL | org.apache.ibatis.reflection. |
1.2.62 | 1818089308493370394 | 0x193b2697eaaed41aL | org.h2.server. |
1.2.62 | 2164696723069287854 | 0x1e0a8c3358ff3daeL | org.apache.ibatis.datasource. |
1.2.62 | 2653453629929770569 | 0x24d2f6048fef4e49L | org.objectweb.asm. |
1.2.62 | 2836431254737891113 | 0x275d0732b877af29L | flex.messaging.util.concurrent. |
1.2.62 | 3089451460101527857 | 0x2adfefbbfe29d931L | org.apache.ibatis.javassist. |
1.2.62 | 3256258368248066264 | 0x2d308dbbc851b0d8L | java.lang.UNIXProcess |
1.2.62 | 3718352661124136681 | 0x339a3e0b6beebee9L | org.apache.ibatis.ognl. |
1.2.62 | 4046190361520671643 | 0x3826f4b2380c8b9bL | com.mysql.cj.jdbc.MysqlConnectionPoolDataSource |
1.2.62 | 4841947709850912914 | 0x43320dc9d2ae0892L | org.codehaus.jackson. |
1.2.62 | 6280357960959217660 | 0x5728504a6d454ffcL | org.apache.ibatis.scripting. |
1.2.62 | 6534946468240507089 | 0x5ab0cb3071ab40d1L | org.apache.commons.proxy. |
1.2.62 | 6734240326434096246 | 0x5d74d3e5b9370476L | com.mysql.cj.jdbc.MysqlXADataSource |
1.2.62 | 7123326897294507060 | 0x62db241274397c34L | org.apache.commons.collections.functors. |
1.2.62 | 8488266005336625107 | 0x75cc60f5871d0fd3L | org.apache.commons.configuration |
1.2.66 | -2439930098895578154 | 0xde23a0809a8b9bd6L | javax.script. |
1.2.66 | -582813228520337988 | 0xf7e96e74dfa58dbcL | javax.sound. |
1.2.66 | -26639035867733124 | 0xffa15bf021f1e37cL | javax.print. |
1.2.66 | 386461436234701831 | 0x55cfca0f2281c07L | javax.activation. |
1.2.66 | 1153291637701043748 | 0x100150a253996624L | javax.tools. |
1.2.66 | 1698504441317515818L | 0x17924cca5227622aL | javax.management. |
1.2.66 | 7375862386996623731L | 0x665c53c311193973L | org.apache.xbean. |
1.2.66 | 7658177784286215602L | 0x6a47501ebb2afdb2L | org.eclipse.jetty. |
1.2.66 | 8055461369741094911L | 0x6fcabf6fa54cafffL | javax.naming. |
1.2.67 | -7775351613326101303L | 0x941866e73beff4c9L | org.apache.shiro.realm. |
1.2.67 | -6025144546313590215L | 0xac6262f52c98aa39L | org.apache.http.conn. |
1.2.67 | -5939269048541779808L | 0xad937a449831e8a0L | org.quartz. |
1.2.67 | -5885964883385605994L | 0xae50da1fad60a096L | com.taobao.eagleeye.wrapper |
1.2.67 | -3975378478825053783L | 0xc8d49e5601e661a9L | org.apache.http.impl. |
1.2.67 | -2378990704010641148L | 0xdefc208f237d4104L | com.ibatis. |
1.2.67 | -905177026366752536L | 0xf3702a4a5490b8e8L | org.apache.catalina. |
1.2.67 | 2660670623866180977L | 0x24ec99d5e7dc5571L | org.apache.http.auth. |
1.2.67 | 2731823439467737506L | 0x25e962f1c28f71a2L | br.com.anteros. |
1.2.67 | 3637939656440441093L | 0x327c8ed7c8706905L | com.caucho. |
1.2.67 | 4254584350247334433L | 0x3b0b51ecbf6db221L | org.apache.http.cookie. |
1.2.67 | 5274044858141538265L | 0x49312bdafb0077d9L | org.javasimon. |
1.2.67 | 5474268165959054640L | 0x4bf881e49d37f530L | org.apache.cocoon. |
1.2.67 | 5596129856135573697L | 0x4da972745feb30c1L | org.apache.activemq.jms.pool. |
1.2.67 | 6854854816081053523L | 0x5f215622fb630753L | org.mortbay.jetty. |
1.2.68 | -3077205613010077203L | 0xd54b91cc77b239edL | org.apache.shiro.jndi. |
1.2.68 | -2825378362173150292L | 0xd8ca3d595e982bacL | org.apache.ignite.cache.jta. |
1.2.68 | 2078113382421334967L | 0x1cd6f11c6a358bb7L | javax.swing.J |
1.2.68 | 6007332606592876737L | 0x535e552d6f9700c1L | org.aoju.bus.proxy.provider. |
1.2.68 | 9140390920032557669L | 0x7ed9311d28bf1a65L | java.awt.p |
1.2.68 | 9140416208800006522L | 0x7ed9481d28bf417aL | java.awt.i |
1.2.69 | -8024746738719829346L | 0x90a25f5baa21529eL | java.io.Serializable |
1.2.69 | -5811778396720452501L | 0xaf586a571e302c6bL | java.io.Closeable |
1.2.69 | -3053747177772160511L | 0xd59ee91f0b09ea01L | oracle.jms.AQ |
1.2.69 | -2114196234051346931L | 0xe2a8ddba03e69e0dL | java.util.Collection |
1.2.69 | -2027296626235911549L | 0xe3dd9875a2dc5283L | java.lang.Iterable |
1.2.69 | -2939497380989775398L | 0xd734ceb4c3e9d1daL | java.lang.Object |
1.2.69 | -1368967840069965882L | 0xed007300a7b227c6L | java.lang.AutoCloseable |
1.2.69 | 2980334044947851925L | 0x295c4605fd1eaa95L | java.lang.Readable |
1.2.69 | 3247277300971823414L | 0x2d10a5801b9d6136L | java.lang.Cloneable |
1.2.69 | 5183404141909004468L | 0x47ef269aadc650b4L | java.lang.Runnable |
1.2.69 | 7222019943667248779L | 0x6439c4dff712ae8bL | java.util.EventListener |
1.2.70 | -5076846148177416215L | 0xb98b6b5396932fe9L | org.apache.commons.collections4.Transformer |
1.2.70 | -4703320437989596122L | 0xbeba72fb1ccba426L | org.apache.commons.collections4.functors |
1.2.70 | -4314457471973557243L | 0xc41ff7c9c87c7c05L | org.jdom2.transform. |
1.2.70 | -2533039401923731906L | 0xdcd8d615a6449e3eL | org.apache.hadoop.shaded.com.zaxxer.hikari. |
1.2.70 | 156405680656087946L | 0x22baa234c5bfb8aL | com.p6spy.engine. |
1.2.70 | 1214780596910349029L | 0x10dbc48446e0dae5L | org.apache.activemq.pool. |
1.2.70 | 3085473968517218653L | 0x2ad1ce3a112f015dL | org.apache.aries.transaction. |
1.2.70 | 3129395579983849527L | 0x2b6dd8b3229d6837L | org.apache.activemq.ActiveMQConnectionFactory |
1.2.70 | 4241163808635564644L | 0x3adba40367f73264L | org.apache.activemq.spring. |
1.2.70 | 7240293012336844478L | 0x647ab0224e149ebeL | org.apache.activemq.ActiveMQXAConnectionFactory |
1.2.70 | 7347653049056829645L | 0x65f81b84c1d920cdL | org.apache.commons.jelly. |
1.2.70 | 7617522210483516279L | 0x69b6e0175084b377L | org.apache.axis2.transport.jms. |
1.2.71 | -4537258998789938600L | 0xc1086afae32e6258L | java.io.FileReader |
1.2.71 | -4150995715611818742L | 0xc664b363baca050aL | java.io.ObjectInputStream |
1.2.71 | -2995060141064716555L | 0xd66f68ab92e7fef5L | java.io.FileInputStream |
1.2.71 | -965955008570215305L | 0xf2983d099d29b477L | java.io.ObjectOutputStream |
1.2.71 | -219577392946377768L | 0xfcf3e78644b98bd8L | java.io.DataOutputStream |
1.2.71 | 2622551729063269307L | x24652ce717e713bbL | java.io.PrintWriter |
1.2.71 | 2930861374593775110L | 0x28ac82e44e933606L | java.io.Buffered |
1.2.71 | 4000049462512838776L | 0x378307cb0111e878L | java.io.InputStreamReader |
1.2.71 | 4193204392725694463L | 0x3a31412dbb05c7ffL | java.io.OutputStreamWriter |
1.2.71 | 5545425291794704408L | 0x4cf54eec05e3e818L | java.io.FileWriter |
1.2.71 | 6584624952928234050L | 0x5b6149820275ea42L | java.io.FileOutputStream |
1.2.71 | 7045245923763966215L | 0x61c5bdd721385107L | java.io.DataInputStream |
1.2.83 | -8754006975464705441L | 0x868385095a22725fL | org.apache.commons.io. |
1.2.83 | -8382625455832334425L | 0x8baaee8f9bf77fa7L | org.mvel2. |
1.2.83 | -6088208984980396913L | 0xab82562f53e6e48fL | kotlin.reflect. |
1.2.83 | -4733542790109620528L | 0xbe4f13e96a6796d0L | com.googlecode.aviator. |
1.2.83 | -1363634950764737555L | 0xed13653cb45c4bedL | org.aspectj. |
1.2.83 | -803541446955902575L | 0xf4d93f4fb3e3d991L | org.dom4j. |
1.2.83 | 860052378298585747L | 0xbef8514d0b79293L | org.apache.commons.cli. |
1.2.83 | 1268707909007641340L | 0x119b5b1f10210afcL | com.google.common.eventbus. |
1.2.83 | 3058452313624178956L | 0x2a71ce2cc40a710cL | org.thymeleaf. |
1.2.83 | 3740226159580918099L | 0x33e7f3e02571b153L | org.junit. |
1.2.83 | 3977090344859527316L | 0x37317698dcfce894L | org.mockito.asm. |
1.2.83 | 4319304524795015394L | 0x3bf14094a524f0e2L | com.google.common.io. |
1.2.83 | 5120543992130540564L | 0x470fd3a18bb39414L | org.mockito.runners. |
1.2.83 | 5916409771425455946L | 0x521b4f573376df4aL | org.mockito.cglib. |
1.2.83 | 6090377589998869205L | 0x54855e265fe1dad5L | com.google.common.reflect. |
1.2.83 | 7164889056054194741L | 0x636ecca2a131b235L | org.mockito.stubbing. |
1.2.83 | 8711531061028787095L | 0x78e5935826671397L | org.apache.commons.codec. |
1.2.83 | 8735538376409180149L | 0x793addded7a967f5L | ognl. |
1.2.83 | 8861402923078831179L | 0x7afa070241b8cc4bL | com.google.common.util.concurrent. |
1.2.83 | 9140416208800006522L | 0x7ed9481d28bf417aL | java.awt.i |
1.2.83 | 9144212112462101475L | 0x7ee6c477da20bbe3L | com.google.common.net. |
目前未知的列表
version | hash | hex-hash | name |
---|---|---|---|
1.2.42 | 33238344207745342 | 0x761619136cc13eL | |
1.2.67 | -831789045734283466L | 0xf474e44518f26736L | |
1.2.71 | 3452379460455804429L | 0x2fe950d3ea52ae0dL | |
1.2.78 | -8614556368991373401L | 0x8872f29fd0b0b7a7L | |
1.2.78 | -5472097725414717105L | 0xb40f341c746ec94fL | |
1.2.78 | -3750763034362895579L | 0xcbf29ce484222325L | |
1.2.78 | -1800035667138631116L | 0xe704fd19052b2a34L | |
1.2.78 | -831789045734283466L | 0xf474e44518f26736L | |
1.2.78 | 33238344207745342L | 0x761619136cc13eL | |
1.2.78 | 3452379460455804429L | 0x2fe950d3ea52ae0dL | |
1.2.78 | 4215053018660518963L | 0x3a7ee0635eb2bc33L | |
1.2.83 | -8614556368991373401L | 0x8872f29fd0b0b7a7L | |
1.2.83 | -3750763034362895579L | 0xcbf29ce484222325L | |
1.2.83 | -1800035667138631116L | 0xe704fd19052b2a34L | |
1.2.83 | 4215053018660518963L | 0x3a7ee0635eb2bc33L |
1.2.25 - 1.2.41 补丁绕过(需要开启AutoType)
1 |
|
payload
1 |
|
调试
很显然与之前的payload相比,com.sun.rowset.JdbcRowSetImpl
类名的前面加了L
、后面加了;
就绕过了黑名单过滤。很明显在TypeUtils#loadClass函数中,如果className以L
开头,分号;
结尾,就会去掉L
和;
,然后在加载。条件时只有开启了autoType
或者expectClass
不为空也就是指定了Class
对象时才会调用TypeUtils.loadClass
加载,否则fastjson
会默认禁止加载该类。
1.2.25-1.2.42 补丁绕过(需要开启AutoType)
1 |
|
payload
1 |
|
调试
在进行检测之前,会先对LLcom.sun.rowset.JdbcRowSetImpl;;过滤掉一次L和;,因此可以双写,是的过滤掉一次之后任然可以绕过黑名单检测。
然后进入TypeUtils#loadClass方法,传入的参数还是LLcom.sun.rowset.JdbcRowSetImpl;;,那为什么可以加载com.sun.rowset.JdbcRowSetImpl呢。
可以看到TypeUtils#loadClass
中会过滤掉L
和;
然后加载类,但是注意我们进入的是TypeUtils
重写的loadClass
,通过这个loadClass
有来到了一次原来的loadClass
,比如loadClassA
里面过滤一次L
和;
,然后调用了loadClassB
,通过loadClassB
再次调用了loadClassA
,在第二次loadClassA
又会过滤一次。过滤完之后又会重复一次什么操作,这是第三次我们检查不到L
和;
于是可以正常加载com.sun.rowset.JdbcRowSetImpl
类了。
1.2.25-1.2.43 补丁绕过(需要开启AutoType)
1 |
|
payload
1 |
|
调试分析
来到checkAutoType函数,这里的className是我们的@type,不过会先讲$替换成.
,这里有个if判断,大概自己试了一下,第一个if判断了是否是L开头;结尾的类名,第二次就是判断是否是LL开头的类名,满足第一个if不满足第二个if就把开头的L和结尾的;去掉然后再去后续操作,满足两个if直接抛出异常。
接着就是白名单和黑名单校验了。不过都是用字符串的哈希值校验
在TypeUtils.loadClass函数中,经过这个函数过后,我们可以加载出来clazz,所以跟进去看看是怎么加载的。
跟到1196行,发现会判断第一个字符是否是[
,然后就会将这个符号去掉再加载类,然后我们就加载出了com.sun.rowset.JdbcRowSetImpl
,然后通过Array.newInstance(componentType, 0).getClass();
这个函数获取返回类。返回的类名是[Lcom.sun.rowset.JdbcRowSetImpl;
接着就去获取该类的反序列化器,然后调用方法去反序列化
然后我们加载的类会赋值给clazz然后通过getComponentType函数得到了com.sun.rowset.JdbcRowSetImpl类。然后调用DefaultJsonParser#parseArray方法。
里面会有一些判断字符后面内容是否是[
,{
的操作,所以我们的payload需要加上[{
。
1.2.25-1.2.45补丁绕过(需要开启AutoType)
前提条件:需要目标服务端存在mybatis的jar包,且版本需为3.x.x系列<3.5.0的版本。
1 |
|
1 |
|
payload
1 |
|
fastjson1.2.46黑名单就加入了该类。
调试分析
可以看到相比于上个版本,这里会校验className第一个字符是否是[
,h1后面有个L表示是长整型。
校验部分肯定可以过得,因为此时黑名单还没有这个类。我们payload设置了properties这个参数,并且这个类是有setProperties方法的,且满足条件,所以会自动调用。
这里就是熟悉的JNDI注入漏洞了,即InitialContext.lookup()
,其中参数由我们输入的properties属性中的data_source值获取的,然后就是jndi注入的部分了。
1.2.25-1.2.47补丁绕过(无需开启AutoType)
- 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport反而不能成功触发;
- 1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用;
1 |
|
绕过的大体思路是通过java.lang.Class,将JdbcRowSetImpl类加载到Map中缓存,从而绕过AutoType的检测。因此将payload分两次发送,第一次加载,第二次执行。默认情况下,只要遇到没有加载到缓存的类,checkAutoType()就会抛出异常终止程序。
payload
1 |
|
绕过的大体思路是通过java.lang.Class,将JdbcRowSetImpl类加载到Map中缓存,从而绕过AutoType的检测。因此将payload分两次发送,第一次加载,第二次执行。默认情况下,只要遇到没有加载到缓存的类,checkAutoType()就会抛出异常终止程序。
不受AutoTypeSupport影响的版本
不受AutoTypeSupport影响的版本为1.2.33-1.2.47,本次调试的是1.2.47版本。
未开启AutoTypeSupport时
第一次在调用checkAutoType
的时候,由于未开启AutoTypeSupport,因此不会进入黑白名单校验的逻辑;由于@type执行java.lang.Class类,在接下来的findClass()函数中直接被找到,并在后面的if判断clazz不为空后直接返回
接着调用了MiscCodec#deserialze函数。
判断键是否为”val”,是的话就会提取val键对应的值赋给objVal变量,而objVal在后面会赋值给strVal变量
接着判断clazz是否为Class类,是的话调用TypeUtils.loadClass()加载strVal变量值指向的类:
在TypeUtils.loadClass()函数中,成功加载com.sun.rowset.JdbcRowSetImpl类后,就会将其缓存在Map中:
在扫描第二部分的JSON数据时,由于前面第一部分JSON数据中的val键值”com.sun.rowset.JdbcRowSetImpl”已经缓存到Map中了,所以当此时调用TypeUtils.getClassFromMapping()时能够成功从Map中获取到缓存的类,进而在下面的判断clazz是否为空的if语句中直接return返回了,从而成功绕过checkAutoType()检测
开启AutoTypeSupport时
在第一部分JSON数据的扫描解析中,由于@type
指向java.lang.Class
,因此即使是开启AutoTypeSupport
先后进行白名单、黑名单校验的情况下都能成功通过检测,之后和前面的一样调用findClass()函数获取到Class类:
关键在于第二部分JSON数据的扫描解析。第二部分的@type指向的是利用类com.sun.rowset.JdbcRowSetImpl
,其中的”com.sun.”是在denyList黑名单中的,但是为何在检测时能成功绕过呢?
我们调试发现,逻辑是先进行白名单再进行黑名单校验,在黑名单校验的if判断条件中是存在两个必须同时满足的条件的:
1 |
|
第一个判断条件Arrays.binarySearch(denyHashCodes, hash) >= 0
是满足的,因为我们的@type包含了黑名单的内容;关键在于第二个判断条件TypeUtils.getClassFromMapping(typeName) == null
,这里由于前面已经将com.sun.rowset.JdbcRowSetImpl
类缓存在Map中了,也就是说该条件并不满足,导致能够成功绕过黑名单校验、成功触发漏洞。
受AutoTypeSupport影响的版本
受AutoTypeSupport影响的版本为1.2.25-1.2.32,本次调试的是1.2.25版本。
未开启AutoTypeSupport时
与前面分析差不多
开启AutoTypeSupport时
少了个判断条件,即TypeUtils.getClassFromMapping(typeName) == null
,从而无法绕过黑名单检测,导致检测到com.sun.rowset.JdbcRowSetImpl
这个黑名单中的类抛出异常。
修复
将下面的true改成false,防止将com.sun.rowset.JdbcRowSetImpl
添加进map缓存里。从而无法使用TypeUtils.getClassFromMapping()
来加载
1.2.5 - 1.2.61 通杀
Fastjson1.2.5 <= 1.2.59
需要开启AutoType
1 |
|
Fastjson1.2.5 <= 1.2.60
需要开启 autoType:
1 |
|
Fastjson1.2.5 <= 1.2.61
1 |
|
1.2.62反序列化漏洞(黑名单绕过)
漏洞原理
新Gadget绕过黑名单限制。
org.apache.xbean.propertyeditor.JndiConverter类的toObjectImpl()函数存在JNDI注入漏洞,可由其构造函数处触发利用。
1 |
|
payload
1 |
|
前提条件
- 需要开启AutoType;
- Fastjson <= 1.2.62;
- JNDI注入利用所受的JDK版本限制;
- 目标服务端需要存在xbean-reflect包;
pom.xml
1 |
|
调试分析
未开启AutoTypeSupport时(无法触发漏洞)
因为是未开启AutoTypeSupport,先黑名单检测,再白名单检测。前面先进行一些检查,比如之前版本绕过检测的[等字符,会检查一遍。然后950行不会进去,因为我们没有开启AutoTypeSupport
接着会尝试一系列方法进行类加载,从Mapping缓存中加载,调用findClass()来加载,从typeMapping中获取,判断是在internalWhite即内部哈希白名单中,则直接加载。这些加载全都失败,clazz任然为null。
接着黑白名单校验,这个类这个fastjson版本不在黑名单中,所以不会被拦截,当然也不再白名单,任然无法加载
接下来会对其是否开启AutoType、是否注解JsonType、是否设置expectClass来进行判断,如果判断通过,就会判断是否开启AutoType或是否注解JsonType,是的话就会在加载clazz后将其缓存到Mappings中,这正是1.2.47的利用点,也就是说,只要开启了AutoType或者注解了JsonType的话,在这段代码逻辑中就会把clazz缓存到Mappings中,但我们JsonType为false,AutoType也没有开启,无法缓存。
然后抛出异常,所以未开启AutoTypeSupport时,无法触发漏洞。
开启AutoTypeSupport时(触发漏洞)
前面部分和之前差不多,来到关键部分,因为这次开启了AutoTypeSupport,所以可以进入TypeUtils#loadClass函数加载该类。
通过AppClassLoader类加载器成功加载恶意类,且由于前面开启AutoType的缘故、cacheClass为true进而开启了cache缓存、使得恶意类缓存到了Mapping中,最后返回加载的类,之后跟之前调试第一条链差不多,反序列化该类,触发构造函数,getter或setter方法。
Gadget分析
就两个方法,那调用的只能是无参构造函数了,并且调用了父类的有参构造函数。
我们PoC中的AsText属性在JndiConverter类中并没有重写该属性的setter方法,但却调用了父类的AbstractConverter类的setAsText()函数来进行setter设置。并且为什么可以确认是这个属性呢,我们的走到jndi的lookup方法,这个方法在toObjectImpl方法里,可以直接找哪里调用了toObjectImpl方法也可以顺着一路找到setAsText()函数。
1.2.66反序列化漏洞(黑名单绕过
原理都大差不差,cv了一下mi1k7ea的文章作为笔记
漏洞原理
新Gadget绕过黑名单限制。
1.2.66涉及多条Gadget链,原理都是存在JDNI注入漏洞。
org.apache.shiro.realm.jndi.JndiRealmFactory类PoC:
1 |
|
br.com.anteros.dbcp.AnterosDBCPConfig类PoC:
1 |
|
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类PoC:
1 |
|
前提条件
- 开启AutoType;
- Fastjson <= 1.2.66;
- JNDI注入利用所受的JDK版本限制;
- org.apache.shiro.jndi.JndiObjectFactory类需要包;
- br.com.anteros.dbcp.AnterosDBCPConfig类需要Anteros-Core和Anteros-DBCP包;
- com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类需要ibatis-sqlmap和jta包;
漏洞复现
漏洞代码示例:
1 |
|
1.2.67反序列化漏洞(黑名单绕过)
漏洞原理
新Gadget绕过黑名单限制。
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类PoC:
1 |
|
org.apache.shiro.jndi.JndiObjectFactory类PoC:
1 |
|
前提条件
- 开启AutoType;
- Fastjson <= 1.2.67;
- JNDI注入利用所受的JDK版本限制;
- org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类需要ignite-core、ignite-jta和jta依赖;
- org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core和slf4j-api依赖;
漏洞复现
漏洞代码示例:
1 |
|
1.2.68反序列化漏洞(expectClass绕过AutoType)
前提条件
- Fastjson <= 1.2.68;
- 利用类必须是expectClass类的子类或实现类,并且不在黑名单中;
漏洞原理
本次绕过checkAutoType()
函数的关键点在于其第二个参数expectClass,可以通过构造恶意JSON数据、传入某个类作为expectClass参数再传入另一个expectClass类的子类或实现类来实现绕过checkAutoType()
函数执行恶意操作。
简单地说,本次绕过checkAutoType()
函数的攻击步骤为:
- 先传入某个类,其加载成功后将作为expectClass参数传入
checkAutoType()
函数; - 查找expectClass类的子类或实现类,如果存在这样一个子类或实现类其构造方法或
setter
方法中存在危险操作则可以被攻击利用;
漏洞复现
简单地验证利用expectClass绕过的可行性,先假设Fastjson服务端存在如下实现AutoCloseable接口类的恶意类VulAutoCloseable:
1 |
|
构造EXP如下:
1 |
|
payload
1 |
|
无需开启AutoType,直接成功绕过CheckAutoType()
的检测从而触发执行:
调试分析
直接进checkAutoType函数,我们可以直接从mappings缓存中找到指定的java.lang.AutoCloseable
类,
往下就是判断clazz是否不是expectClass类的继承类且不是HashMap类型,是的话抛出异常,否则直接返回该类。这里没有expectClass就直接返回java.lang.AutoCloseable
类
接着获取java.lang.AutoCloseable
这个类的反序列化器,然后调用其deserialze方法开始反序列化。即JavaBeanDeserializer#deserialze
往下的逻辑,就是根据给定的 typeName
获取一个对象反序列化器(ObjectDeserializer
),如果该反序列化器不存在,则尝试根据类型信息和配置自动确定一个合适的反序列化器。调用配置对象的 checkAutoType
方法检查和自动确定给定类型名(typeName
)是否允许并返回具体的用户类型。expectClass
是期望的类,lexer.getFeatures()
是一些特征或配置选项。
往下,由于java.lang.AutoCloseable类并非其中黑名单中的类,因此expectClassFlag被设置为true
往下,由于expectClassFlag为true且目标类不在内部白名单中,程序进入AutoType开启时的检测逻辑
由于vul.VulAutoCloseable类不在黑白名单中,因此这段能通过检测并继续往下执行。
往下,未加载成功目标类,就会进入AutoType关闭时的检测逻辑,和上同理,这段能通过检测并继续往下执行
由于expectClassFlag为true,进入如下的loadClass()逻辑来加载目标类,但是由于AutoType关闭且jsonType为false,因此调用loadClass()函数的时候是不开启cache即缓存的
跟进,使用了AppClassLoader加载vul.VulAutoCloseable类并直接返回加载的类。
往下,判断是否jsonType、true的话直接添加Mapping缓存并返回类,否则接着判断返回的类是否是ClassLoader、DataSource、RowSet等类的子类,是的话直接抛出异常,这也是过滤大多数JNDI注入Gadget的机制。如果expectClass不为null,则判断目标类是否是expectClass类的子类,是的话就添加到Mapping缓存中并直接返回该目标类,否则直接抛出异常导致利用失败,这里就解释了为什么恶意类必须要继承AutoCloseable接口类,因为这里expectClass为AutoCloseable类、因此恶意类必须是AutoCloseable类的子类才能通过这里的判断:
checkAutoType()调用完返回类之后,就进行反序列化操作、新建恶意类实例进而调用其构造函数从而成功触发漏洞
小结:当传入checkAutoType()函数的expectClass参数不为null,并且需要加载的目标类是expectClass类的子类或者实现类时(不在黑名单中),就将需要加载的目标类当做是正常的类然后通过调用TypeUtils.loadClass()函数进行加载。
实战利用
前面漏洞复现只是简单地验证绕过方法的可行性,在实际的攻击利用中,是需要我们去寻找实际可行的利用类的。
b1ue大佬文章中提出可以寻找关于输入输出流的类来写文件,IntputStream和OutputStream都是实现自AutoCloseable接口的。
复制文件(任意文件读取漏洞)
利用类:org.eclipse.core.internal.localstore.SafeFileOutputStream
依赖:
1 |
|
其SafeFileOutputStream(java.lang.String, java.lang.String)
构造函数判断了如果targetPath文件不存在且tempPath文件存在,就会把tempPath复制到targetPath中,可以利用造成任意文件读取。
1 |
|
写入文件
写内容类:com.esotericsoftware.kryo.io.Output
依赖:
1 |
|
Output类主要用来写内容,它提供了setBuffer()和setOutputStream()两个setter方法可以用来写入输入流,其中buffer参数值是文件内容,outputStream参数值就是前面的SafeFileOutputStream类对象,而要触发写文件操作则需要调用其flush()函数
1 |
|
接着,就是要看怎么触发Output类flush()函数了,flush()函数只有在close()和require()函数被调用时才会触发,其中require()函数在调用write相关函数时会被触发。
其中,找到JDK的ObjectOutputStream类,其内部类BlockDataOutputStream的构造函数中将OutputStream类型参数赋值给out成员变量,而其setBlockDataMode()函数中调用了drain()函数、drain()函数中又调用了out.write()函数,满足前面的需求,对于setBlockDataMode()函数的调用,在ObjectOutputStream类的有参构造函数中就存在。
但是Fastjson优先获取的是ObjectOutputStream类的无参构造函数,因此只能找ObjectOutputStream的继承类来触发了。
只有有参构造函数的ObjectOutputStream继承类:com.sleepycat.bind.serial.SerialOutput
SerialOutput类的构造函数中是调用了父类ObjectOutputStream的有参构造函数,这就满足了前面的条件了。
依赖:
1 |
|
PoC如下,用到了Fastjson循环引用的技巧来调用:
1 |
|
补丁分析
看GitHub官方的diff,主要在ParserConfig.java中:https://github.com/alibaba/fastjson/compare/1.2.68%E2%80%A61.2.69#diff-f140f6d9ec704eccb9f4068af9d536981a644f7d2a6e06a1c50ab5ee078ef6b4
在expectClass的判断逻辑中,对类名进行了Hash处理再比较哈希黑名单,并且添加了三个类
网上已经有了利用彩虹表碰撞的方式得到的新添加的三个类分别为:
版本 | 十进制Hash值 | 十六进制Hash值 | 类名 |
---|---|---|---|
1.2.69 | 5183404141909004468L | 0x47ef269aadc650b4L | java.lang.Runnable |
1.2.69 | 2980334044947851925L | 0x295c4605fd1eaa95L | java.lang.Readable |
1.2.69 | -1368967840069965882L | 0xed007300a7b227c6L | java.lang.AutoCloseable |
这就简单粗暴地防住了这几个类导致的绕过问题了。
SafeMode
官方参考:https://github.com/alibaba/fastjson/wiki/fastjson_safemode
在1.2.68之后的版本,在1.2.68版本中,fastjson增加了safeMode的支持。safeMode打开后,完全禁用autoType。所有的安全修复版本sec10也支持SafeMode配置。
代码中设置开启SafeMode如下:
1 |
|
开启之后,就完全禁用AutoType即@type
了,这样就能防御住Fastjson反序列化漏洞了。
具体的处理逻辑,是放在checkAutoType()函数中的前面,获取是否设置了SafeMode,如果是则直接抛出异常终止运行
payload
JdbcRowSetImpl
1 |
|
TemplatesImpl
1 |
|
JndiDataSourceFactory
1 |
|
SimpleJndiBeanFactory
1 |
|
DefaultBeanFactoryPointcutAdvisor
1 |
|
WrapperConnectionPoolDataSource
1 |
|
JndiRefForwardingDataSource
1 |
|
InetAddress
1 |
|
Inet6Address
1 |
|
URL
1 |
|
JSONObject
1 |
|
URLReader
1 |
|
AutoCloseable 任意文件写入
1 |
|
BasicDataSource
1 |
|
JndiConverter
1 |
|
JtaTransactionConfig
1 |
|
JndiObjectFactory
1 |
|
AnterosDBCPConfig
1 |
|
AnterosDBCPConfig2
1 |
|
CacheJndiTmLookup
1 |
|
AutoCloseable 清空指定文件
1 |
|
AutoCloseable 清空指定文件
1 |
|
AutoCloseable 任意文件写入
1 |
|
AutoCloseable MarshalOutputStream 任意文件写入
1 |
|
BasicDataSource
1 |
|
HikariConfig
1 |
|
HikariConfig
1 |
|
HikariConfig
1 |
|
HikariConfig
1 |
|
SessionBeanProvider
1 |
|
JMSContentInterceptor
1 |
|
ContextClassLoaderSwitcher
1 |
|
OracleManagedConnectionFactory
1 |
|
JNDIConfiguration
1 |
|
JDBC4Connection
1 |
|
LoadBalancedMySQLConnection
1 |
|
UnpooledDataSource
1 |
|
LoadBalancedMySQLConnection2
1 |
|
ReplicationMySQLConnection
1 |
|
fastjson备忘录
Fastjson/README.md at master · safe6Sec/Fastjson (github.com)