简介
这里直接引用别的师傅的
Jackson是一个流行的Java库,用于处理JSON格式的数据。它提供了一组功能强大的工具,可以方便地在Java对象和JSON之间进行转换。以下是关于Jackson库的介绍:
Jackson库包括三个主要的模块:jackson-databind
、jackson-annotations
和jackson-core
。
jackson-databind
模块:这是Jackson库的核心模块,提供了将Java对象序列化为JSON格式以及将JSON格式反序列化为Java对象的功能。它包含了ObjectMapper
类,用于执行对象和JSON之间的转换操作,以及一些注解(如@JsonProperty
、@JsonIgnore
等),用于控制对象和JSON属性之间的映射关系。jackson-annotations
模块:这个模块包含了一些用于对Java对象进行标记的注解,用于指示Jackson库如何处理对象的序列化和反序列化。这些注解使得开发人员可以在对象上方便地定义与JSON映射相关的信息,例如指定属性名、忽略某些属性等。jackson-core
模块:这个模块提供了用于处理JSON数据的低级API,例如读取和写入JSON流、构建JSON树结构等。虽然大多数开发人员更倾向于使用jackson-databind
模块,但在一些特定的场景下,直接使用jackson-core
模块也是非常有用的。
Jackson 功能很强大,既能满足简单的序列化和反序列化操作,也能实现复杂的、个性化的序列化和反序列化操作。到目前为止,Jackson 的序列化和反序列化性能都非常优秀,已经是国内外大部分 JSON 相关编程的首选工具。Jackson从 2.0 开始改用新的包名 fasterxml,1.x 版本的包名是 codehaus。除了包名不同,他们的 Maven artifact id 也不同。1.x 版本现在只提供 bug-fix,而 2.x 版本还在不断开发和发布中。如果是新项目,建议直接用 2x,即 fasterxml jackson。
序列化和反序列化
1 | public static void main(String[] args) throws IOException { |
这里只是简单的序列化和反序列化
多态问题的解决
直接引用Mi1k7ea师傅的解释
简单地说,Java多态就是同一个接口使用不同的实例而执行不同的操作。
那么问题来了,如果对多态类的某一个子类实例在序列化后再进行反序列化时,如何能够保证反序列化出来的实例即是我们想要的那个特定子类的实例而非多态类的其他子类实例呢?——Jackson实现了JacksonPolymorphicDeserialization机制来解决这个问题。
JacksonPolymorphicDeserialization即Jackson多态类型的反序列化:在反序列化某个类对象的过程中,如果类的成员变量不是具体类型(non-concrete),比如Object、接口或抽象类,则可以在JSON字符串中指定其具体类型,Jackson将生成具体类型的实例。
简单地说,就是将具体的子类信息绑定在序列化的内容中以便于后续反序列化的时候直接得到目标子类对象,其实现有两种,即DefaultTyping和@JsonTypeInfo注解。
DefaultTyping
Jackson提供一个enableDefaultTyping设置,其包含4个值,查看jackson-databind-2.7.9.jar!/com/fasterxml/jackson/databind/ObjectMapper.java可看到相关介绍信息
1 | public enum DefaultTyping { |
默认情况下,即无参数的enableDefaultTyping是第二个设置,OBJECT_AND_NON_CONCRETE。
JAVA_LANG_OBJECT
当被序列化或反序列化的类里的属性被声明为一个Object类型时,会对该Object类型的属性进行序列化和反序列化,并且明确规定类名。(当然,这个Object本身也得是一个可被序列化的类)
加入一个测试类
改一下Person的属性
测试
1 | zbz zbz1 = new zbz(); |
可以看到序列化之后的属性带有类名并且直接zbz的对象
OBJECT_AND_NON_CONCRETE
OBJECT_AND_NON_CONCRETE:除了前面提到的特征,当类里有Interface、AbstractClass类时,对其进行序列化和反序列化(当然这些类本身需要时合法的、可被序列化的对象)。此外,enableDefaultTyping()默认的无参数的设置就是此选项。
引入一个接口进行测试
1 | zbz zbz1 = new zbz(); |
可以看到接口序列化成功
NON_CONCRETE_AND_ARRAYS
NON_CONCRETE_AND_ARRAYS:除了前面提到的特征外,还支持Array类型。
在改造一下改成带有数组的形式
1 | zbz[] zbz1 = new zbz[2]; |
输出看到,类名变成了”[L”+类名+”;”,序列化Object之后为数组形式,反序列化之后得到[Lcom.mi1k7ea.Hacker;类对象,说明对Array类型成功进行了序列化
NON_FINAL
NON_FINAL:除了前面的所有特征外,包含即将被序列化的类里的全部、非final的属性,也就是相当于整个类、除final外的属性信息都需要被序列化和反序列化。
1 | zbz[] zbz1 = new zbz[2]; |
成功对非final属性的zbz进行了序列化
从前面的分析知道,DefaultTyping的几个设置选项是逐渐扩大适用范围的,如下表:
DefaultTyping类型 | 描述说明 |
---|---|
JAVA_LANG_OBJECT | 属性的类型为Object |
OBJECT_AND_NON_CONCRETE | 属性的类型为Object、Interface、AbstractClass |
NON_CONCRETE_AND_ARRAYS | 属性的类型为Object、Interface、AbstractClass、Array |
NON_FINAL | 所有除了声明为final之外的属性 |
@JsonTypeInfo注解
@JsonTypeInfo注解是Jackson多态类型绑定的一种方式,支持下面5种类型的取值:
1 |
JsonTypeInfo.Id.NONE
Person类,给object属性添加@JsonTypeInfo注解,指定为JsonTypeInfo.Id.NONE:
1 | public int age; |
进行序列化
1 | Person person = new Person(); |
输出看到,和没有设置值为JsonTypeInfo.Id.NONE的@JsonTypeInfo注解是一样的。
JsonTypeInfo.Id.CLASS
给Person类指定注解为JsonTypeInfo.Id.CLASS
1 | {"age":0,"name":null,"object":{"@class":"com.ocean.zbz","name":"zbz"},"address":null,"zbz":null} |
可以看到输出种多了”@class”:”com.ocean.zbz”,含有具体的class信息
JsonTypeInfo.Id.MINIMAL_CLASS
给Person类指定JsonTypeInfo.Id.MINIMAL_CLASS
1 | {"age":0,"name":null,"object":{"@c":"com.ocean.zbz","name":"zbz"},"address":null,"zbz":null} |
输出看到,object属性中多了”@c”:”com.ocean.zbz”,即使用@c替代料@class,官方描述中的意思是缩短了相关类名,实际效果和JsonTypeInfo.Id.CLASS类似。
JsonTypeInfo.Id.NAME
给Person类指定JsonTypeInfo.Id.NAME
1 | {"age":0,"name":null,"object":{"@type":"zbz","name":"zbz"},"address":null,"zbz":null} |
输出看到,object属性中多了”@type”:”zbz”,但没有具体的包名在内的类名,因此在后面的反序列化的时候会报错,也就是说这个设置值是不能被反序列化利用的:
JsonTypeInfo.Id.CUSTOM
其实这个值时提供给用户自定义的意思,我们是没办法直接使用的,需要手动写一个解析器才能配合使用,直接运行会抛出异常:
由前面测试发现,当@JsonTypeInfo注解设置为如下值之一并且修饰的是Object类型的属性时,可以利用来触发Jackson反序列化漏洞:
- JsonTypeInfo.Id.CLASS
- JsonTypeInfo.Id.MINIMAL_CLASS
这里都是参考自Mi1k7ea师傅的只是自己重新敲了一遍可以去看原文很易懂
反序列化分析
DefaultTyping
1 | Person person = new Person(); |
在序列化时会调用构造函数 和getter方法
在反序列化时会调用 构造函数和setter方法
@JsonTypeInfo注解
输出看到,和使用DefaultTyping是一样的
调试分析
首先跟到_readMapAndClose方法中
这里会调用deserialize方法进行反序列化跟进去
这里又会调用vanillaDeserialize方法跟进去