Jackson反序列化
Jackson基础
Jackson介绍
Jackson 是一个开源的Java序列化和反序列化工具,可以将 Java 对象序列化为 XML 或 JSON 格式的字符串,以及将 XML 或 JSON 格式的字符串反序列化为 Java 对象。本次Jackson反序列化使用Jackson版本为2.7.9,导入依赖如下
1 | <dependencies> |
Jackson序列化及反序列化
首先我们自定义一个Person类
1 | public class Person { |
然后编写一个Jackson的序列化与反序列化的Demo。使用writeValueAsString
方法可以将一个对象序列化为Json字符串,而使用readValue
方法可以将一个字符串反序列化为一个Java对象,漏洞的触发点就在readValue
处
1 | public class JacksonDemo { |
Person类序列化后的json字符串如下
1 | {"age":18,"name":"CurlySean"} |
Jackson对于多态问题的解决
将一个多态类的某一个子类序列化后,再进行反序列化时,该怎么确保反序列化出的实例,是我们想要的那个实例呢?Jackson使用JacksonPolymorphicDeserialization机制来解决这个问题:
JacksonPolymorphicDeserialization即Jackson多态类的反序列化。在反序列化的过程中,如果类的成员变量不是举例类型,例如Object、Interface(接口)或Abstrack(抽象类),则可以在Json字符串中指定其具体类型,Jackson将生成具体类型的实例。即可以将具体子类的信息绑定在序列化的内容中,在后续的反序列化过程中,可以直接得到目标子类对象,其中有两种实现方式:
- DefaultTyping
- @JsonTypeInfo
DefaultTping
在Jackson中提供了enableDefaultTyping设置,其中包含了四个值:
- JAVA_LANG_OBJECT
- OBJECT_AND_NON_CONCRETE
- NON_CONCRETE_AND_ARRAYS
- NON_FINAL
在设置enableDefalutTyping
时,默认OBJECT_AND_NON_CONCRETE
JAVA_LANG_OBJECT
JAVA_LANG_OBJECT:当被序列化或反序列化的类里的属性被声明为Object类型时,会对该Object类型的属性进行序列化和反序列化,并且明确规定类名
我们添加一个Hacker类
1 | public class Hacker { |
同时在Person类中,添加一个Object类型的属性
1 | public class Person { |
新建一个JAVA_LANG_OBJECTdemo.java
,添加enableDefaultTyping()
,将其设置为JAVA_LANG_OBJECT
1 | public class JAVA_LANG_OBJECTdemo { |
运行后输出序列化json字符串如下。我们和未设置JAVA_LANG_OBJECT
情况下的序列化字符串做一下对比,发现在设置JAVA_LANG_OBJECT
时,Object类型的属性格式为key:["全类名",{"key":"value"}]
(有点像fastjson中的@Type
)
1 | //设置JAVA_LANG_OBJECT |
OBJECT_AND_NON_CONCRETE
OBJECT_AND_NON_CONCRETE:除了JAVA_LANG_OBJECT
的特征,在类中有Interface和Abstract时,也会将其序列化和反序列化(一切的前提是这些类本身就是合法的可序列化的对象)
我们添加一个Sex接口
1 | public interface Sex { |
添加一个Sex接口的实现类MySex
1 | public class MySex implements Sex{ |
然后再Person类中添加sex属性
1 | public class Person { |
修改序列化和反序列化代码
1 | public class OBJECT_AND_NON_CONCRETEdemo { |
同样我们来对比一下在设置和没有设置情况下序列化字符串的区别
1 | //设置OBJECT_AND_NON_CONCRETE |
但是未设置OBJECT_AND_NON_CONCRETE
时,反序列化的操作就不是那么顺利了,由于接口类的原因,并不能成功的反序列化,抽象类同理
NON_CONCRETE_AND_ARRAYS
NON_CONCRETE_AND_ARRAYS:在除前面Object、接口类和抽象类以外,多支持了Array数组类型
同样编写测试代码
1 | public class NON_CONCRETE_AND_ARRAYSdemo { |
设置NON_CONCRETE_AND_ARRAYS
的情况下,数组类会在全类名前添加[L
,代表这是一个Array类,value的内容格式为["key1":"value1","key2":"value2"......]
1 | {"age":20,"name":"CurlySean","hacker":["[Lcom.Jackson.Hacker;",[{"name":"xxx"},{"name":"xxx"}]],"sex":["com.Jackson.MySex",{"sex":0}]} |
NON_FINAL
NON_FINAL:在整个类中,除final外的属性都会被序列化和反序列化
在Person类中添加final的secret属性
1 | public class Person { |
修改测试Demo
1 | public class NON_FINALdemo { |
运行后输出的json字符串如下,可以看到是十分详细了,但我们的final的secret属性并没有被序列化进来
1 | ["com.Jackson.Person",{"age":20,"name":"CurlySean","hacker":["[Lcom.Jackson.Hacker;",[["com.Jackson.Hacker",{"name":"xxx"}],["com.Jackson.Hacker",{"name":"xxx"}]]],"sex":["com.Jackson.MySex",{"sex":0}]}] |
@JsonTypeInfo
@JsonTypeInfo
注解是Jackson多态类绑定的一种方式,取值支持以下五种
- @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
- @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
- @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
- @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
- @JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM)
NONE
测试代码是一样的,主要是在Person类中配置1 | public class NONETEST { |
新建一个Student类,给Object类型属性添加@JsonTypeInfo
注解,并指定属性为JsonTypeInfo.Id.NONE
1 | public class Student { |
可以看到,设置和没有设置JsonTypeInfo.Id.NONE
是一样的
1 | {"age":20,"name":"CurlySean","friend":{"name":"xxx"}} |
CLASS
修改Student类@JsonTypeInfo
注解的值为JsonTypeInfo.Id.CLASS
1 |
运行后输出以下内容,其中为Object类的属性,多了"@class":"com.Jackson.Hacker"
,即含有具体的全类名信息。并且我们可以看到,在反序列化出来的Object类型的属性,也可以对应上我们指定的类型
1 | {"age":20,"name":"CurlySean","friend":{"@class":"com.Jackson.Hacker","name":"xxx"}} |
那么在反序列化的过程中,如果使用了JsonTypeInfo.Id.CLASS
进行修饰,那么就可以通过@class
的方式指定类,进行相关的调用
MINIMAL_CLASS
同样修改Student类中对于Object类型属性所设置的@JsonTypeInfo
注解值为JsonTypeInfo.Id.MINIMAL_CLASS
1 |
运行后查看输出,唯一的区别就是@c
替换了@class
,剩下的和CLASS
注解值是一样的
1 | {"age":20,"name":"CurlySean","friend":{"@c":"com.Jackson.Hacker","name":"xxx"}} |
NAME
修改注解值为JsonTypeInfo.Id.NAME
1 |
可以看到,在对于Object类属性序列化时,使用"@type":"className"
的方式指定类,但是没有具体的包名,这也就导致了后面在反序列化时会报错,因此该设置值不能被反序列化利用
JsonTypeInfo.Id.CUSTOM
其实这个值时提供给用户自定义的意思,我们是没办法直接使用的,需要手动写一个解析器才能配合使用,直接运行会抛出异常
小结
根据前面的测试,当@JsonTypeInfo
的注解值为以下两者之一,来修饰Object类的属性时,就可以用来触发Jackson反序列化漏洞
- JsonTypeInfo.Id.CLASS
- JsconTypeInfo.Id.MINIMAL_CLASS
类属性方法的调用
对于fastjson、snakeyaml等依赖,也是提供字符串与Java对象之间的转化。之前学习过,这两种依赖在反序列化的过程中,会调用对应类的构造方法和部分setter与getter方法进行对象属性的赋值,那么Jackson也是同理
这里看一下两种实现方式是否有区别
DefaultTyping
这里我们新增一个Teacher类1 | public class Teacher { |
为MySex类设置setter、getter与构造方法
1 | public class MySex implements Sex{ |
编写测试代码,配置无参数的enableDefaultTyping()
1 | public class DeserializationTest { |
看到输出,可以知道在反序列化的过程中,调用了MySex类的setter方法与无参构造方法
@JsonTypeInfo 注解
修改Teacher类,在Sex类的前面添加注解
1 | public class Teacher { |
在刚刚的测试代码中,将objectMapper.enableDefaultTyping();
部分注释掉
运行后看到,和使用 DefaultTyping 是一样的
反序列化调试分析
调试分析
在将json字符串反序列化为Java对象的过程中,主要操作只有两步:
- 通过构造函数生成实例
- 通过setter方法设置实例属性值
我们给Teacher类添加一个构造函数,然后在Teacher类、MySex类的构造函数、setter方法和getter方法打断点
在readValue处打一个断点,我们进行调试分析
跟进到_readMapAndClose
方法中,首先进行了JsonToken的初始化,然后调用了deserialize
方法
继续跟进deserialize
方法,如果是在第一次初始化时,我们就会走到vanillaDeserialize
方法处
走入vanillaDeserialize
,在该方法中首先调用了createUsingDefault
方法,this._defaultCreator.call
方法调用了对应类的无参构造方法并生成实例
接着我们回到vanillaDeserialize
方法,这里会遍历我们Tearch类的属性值,从根据Json字符串解析的key:Value
键值对中找到对应的值,调用deserializeAndSet
方法,该方法用来调用对应属性的setter方法进行赋值
跟进deserializeAndSet
,继续跟进deserialize
方法,这里判断该节点的数据类型是否为NULL,如果不为NULL,则继续判断_valueTypeDeserializer
是否为NULL,如果不是NULL则继续调用this._valueDeserializer.deserialize
方法
在VALUE_NUMBER_INT
为NULL的情况下调用getIntValue
方法,否则调用_parseInteger
,调用完成之后,会返回JSON键值对中的值
回到deserializeAndSet
方法,接下来调用this._field.set
,该方法就用来调用对应属性的setter方法了(在该属性有setter方法的前提下)
对于name属性的设置,也是一样的。不一样的是,在我们对sex属性设置时,调用deserialize
方法,我们会走到deserializeWithType
方法处
跟进deserializeWithType
方法,因为sex的value值是一个数组,这里返回的result为null,因此后面我们会走到deserializeTypedFromObject
方法
跟进deserializeTypedFromObject
,同样因为是数组,我们会走到else if中,这里调用了_deserializeTypedUsingDefaultImpl
方法
首先调用_findDefaultImplDeserializer
方法,还是因为数组原因,并不会匹配任意一项,最后会调用deserializeTypedFromAny
方法从已有类里去找
跟进deserializeTypedFromAny
,继续跟进_deserialize
方法,该方法中调用this._findDeserializer(ctxt, typeId);
找到了我们的com.Jackson.MySex
类
接着就会调用deserialize
方法,后续的流程就和之前所说一样了,有点那种递归的意思
小结
至此整个调用流程就结束了,同理使用DefaultTyping也是一样的。
简单来说,就是先调用构造函数,生成一个对应类的实例,然后根据JSON的内容,调用对应属性的SETTER方法进行赋值操作,和之前的fastjson反序化是挺像的,但是调用对应setter方法的限制应该是没有fastjson多
Jackson反序列化漏洞
这里就不写Jackson反序列化漏洞的demo了,原理和fastjson等都是差不多的
前提条件
只要满足以下条件之一,就存在Jackson反序列化漏洞:
- 调用
ObjectMapper.enableDefaultTying
函数 - 对要进行反序列化类的属性配置了注解值为
JsonTypeInfo.Id.CLASS
的@JsonTypeInfo
注解 - 对要进行反序列化类的属性配置了注解值为
JsonTypeInfo.Id.MINIMAL_CLASS
的@JsonTypeInfo
注解
Jackson反序列化CVE-2017-7525
该反序列化链是基于 TemplatesImpl 的利用链影响版本
该漏洞的影响版本如下:- Jackson < 2.6.7.1
- Jackson < 2.7.9.1
- Jackson < 2.8.8.1
限制条件
由于该链是基于 TemplatesImpl 的利用链,所以JDK版本为7u21或8u20,使用动态代理的链子漏洞
漏洞复现
新建一个Test对象,如果不使用enableDefaultTyping
方法,则需要配置@JasonTypeInfo
注解
1 | public class Test { |
创建我们的恶意类,编译为class文件(本身想用javassit生成,但是感觉没有这个方便)
1 | public class calcTest extends AbstractTranslet { |
Poc如下
1 | public class Poc { |
看完Poc后,根据前面分析的会调用对应属性的setter方法进行赋值,但是这条链子明显和getgetOutputProperties
方法有关,问了一下AI,说:在反序列化的特殊情况下,只有getter方法没有setter方法,会调用getter方法,可以仔细再去分析一下源码
分析调试
我们在这里的漏洞分析调试,是对于getgetOutputProperties
方法也就是getter方法触发的分析,对于TemplatesImpl 利用链,可以自行找文章分析或看我之前的文章
前面的部分我们不再调试,我们走到vanilaDeserialize
方法处,一直步进到处理outputProperties
方法处,在该方法中调用了this._beanProperties.find
方法,我们看到_beanProperties
的内容
1 | Properties=[uriresolver([simple type, class javax.xml.transform.URIResolver]), transletBytecodes([array type, component type: [array type, component type: [simple type, class byte]]]), stylesheetDOM([simple type, class com.sun.org.apache.xalan.internal.xsltc.DOM]), transletName([simple type, class java.lang.String]), outputProperties([map type; class java.util.Properties, [simple type, class java.lang.String] -> [simple type, class java.lang.String]])] |
this._beanProperties.find
返回一个SetterLessProperty
对象,从名字上看,就知道是在该属性值没有setter方法的情况下返回的对象,并且我们能在里面看到getter方法也就是getgetOutputProperties
继续向下调用prop.deserializeAndSet
,也就是SetterlessProperty.deserializeAndSet
,跟进看一看,就可以发现,这里不再调用setter方法而是getter方法,因此可以触发我们的payload
最后走到getgetOutputProperties
方法触发漏洞
高版本JDK限制
在一些大版本下,JDK1.7、JDK1.8中的TemplatesImpl类是有所区别的
大版本下,无法触发payload的原因在于,新建TransletClassLoader类的代码中,调用了_factory
属性,但是该属性为null,因此会抛出异常
同时在Jackson中也无法设置_tfactory
,因为该属性没有对应的getter和setter方法
补丁修复
在调用 BeanDeserializerFactory.createBeanDeserializer
函数创建 Bean 反序列化器的时候,其中会调用 checkIllegalTypes
函数提取当前类名,然后使用黑名单进行过滤: