前言
ackson是一个开源的Java序列化和反序列化工具,可以将Java对象序列化为XML或JSON格式的字符串,以及将XML或JSON格式的字符串反序列化为Java对象。
依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.7.9</version>
</dependency>
JACKSON序列化与反序列化
Jackson提供了ObjectMapper.writeValueAsString()和ObjectMapper.readValue()两个方法来实现序列化和反序列化的功能,不难看出 write就是序列化,而read 就是反序列化。
demo
package JACKSON;
public class User {
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}
测试:
package JACKSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JacksonTest {
public static void main(String[] args) throws IOException {
User user = new User("snowy","123456");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
System.out.println(json);
User other = mapper.readValue(json,User.class);
System.out.println(other);
}
}
运行结果
感觉和fastjson 差不多,需要解决多态的问题,在fastjson中引入的是@type解决该问题
Jackson中也有与之对应的方法,Jackson实现了JacksonPolymorphicDeserialization机制来解决这个问题,有两种方法:DefaultTyping和@JsonTypeInfo注解。
DefaultTyping
Jackson提供一个enableDefaultTyping设置,其包含4个值,查看com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping可看到相关介绍信息:
默认情况下 DefaultTyping是第二个设置,也就是 OBJECT_AND_NON_CONCRETE
JAVA_LANG_OBJECT
JAVA_LANG_OBJECT:当被序列化或反序列化的类里的属性被声明为一个Object类型时,会对该Object类型的属性进行序列化和反序列化,并且明确规定类名。(当然,这个Object本身也得是一个可被序列化的类)
demo - User2
public class User2 {
public String name="snowy";
}
在User类里添加一个Object属性,并实现getter、setter、toString等方法
test
public class JacksonTest {
public static void main(String[] args) throws IOException {
User user = new User("snowy","777",new user2());
ObjectMapper mapper = new ObjectMapper();
//JAVA_LANG_OBJECT
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
String json = mapper.writeValueAsString(user);
System.out.println(json);
User other = mapper.readValue(json,User.class);
System.out.println(other);
}
}
运行结果
通过对比可以看出,设置JAVA_LANG_OBJECT后,会对Object属性对象进行了序列化和反序列化操作,会将对应的类名一并输出。
OBJECT_AND_NON_CONCRETE
默认操作,当类里有Interface、AbstractClass类时,对其进行序列化和反序列化。
NON_CONCRETE_AND_ARRAYS
支持Array类型的反序列化和序列化
NON_FINAL
支持除声明为final之外的类型。
总结
@JsonTypeinfo 注解
@JsonTypeinfo 注解是 JACKSON多态类型绑定的一种方式,支持下面5种类型的取值:
@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)
JSonTypeinfo.Id.None:
默认值,设不设置都一样。
JsonTypeInfo.Id.CLASS:
在User 添加 jsontypeinfo注解
运行结果:
设置前:
{"username":"snowy","password":"777","object":["JACKSON.user2",{"name":"snowy2","passwd":"user2"}]}
JACKSON.User@7d0587f1
设置后:
{"username":"snowy","password":"777","object":{"@class":"JACKSON.user2","name":"snowy2","passwd":"user2"}}
JACKSON.User@2833cc44
输出看到 object 属性中多了 "@class":"JACKSON.user2",即含有具体的类的信息。同时反序列化出来的object 属性 user2类对象,即能够成功对指定类型进行序列化和反序列化。也就是说,在Jackson 反序列化的时候如果使用了 JsonTypeInfo.Id.CLASS 修饰的话,可以通过@class 的方式指定相关类,并进行相关调用
JsonTypeInfo.Id.MINIMAL_CLASS
修改 User 类中的 object 属性 @jsonTypeinfo 注解值为 JsonTypeinfo.ld.MINIMAL_CLASS.
@JsonTypeInfo( use = JsonTypeInfo.Id.MINIMAL_CLASS)
运行结果:
{"username":"snowy","password":"777","object":{"@c":"JACKSON.user2","name":"snowy2","passwd":"user2"}}
JACKSON.User@27a8c74e
输出看到 object 属性多了 "@c":"JACKSON.user2" ,即使用 @c 代替了 @class ,官方描述中的意思是缩短了相关类名,实际上 效果和 Jsontypeinfo.ld.class是类似的。能够成功对指定类型进行序列化和反序列化都可以用于指定相关类并进行调用
JsonTypeInfo.Id.NAME
修改User类中的 @JsonTypeInfo 注解为 JsonTypeInfo.Id.NAME
@JsonTypeInfo( use = JsonTypeInfo.Id.NAME)
输出结果
{"username":"snowy","password":"777","object":{"@type":"user2","name":"snowy2","passwd":"user2"}}
输出能看到 object 属性多了 "@type":"user2",但是没有具体的包名再内的类名。因此再后面反序列化的时候会发生报错,也就是说,这个设置是不能被反序列化利用 的 ,如图所示
JsonTypeinfo.id.CUSTOM
这个值是提供给用户自定义的意思,我们是无法直接使用的,需要手动写一哥解析器才能配合使用,直接运行会抛出异常
Exception in thread "main" java.lang.IllegalStateException: Do not know how to construct standard type id resolver for idType: CUSTOM
小结:
由前面得知,当@JsonTypeInfo 注解设置为如下 值之一 并且修饰的是Object属性时,可以利用来触发Jackson 反序列化漏洞
- JsonTypeInfo.Id.CLASS (@class)
- JsonTypeInfo.Id.MINIMAL_CLASS (@c)
调试分析
Jackson反序列化的过程 其实就分为两步,第一步是通过构造函数生成实例,第二部是设置实例的属性值。
我们以第一个例子来进行Jackson 反序列化过程的调试分析,在
User other = mapper.readValue(json,User.class);
处打上断点进行调试,同时在getter stter 上打断点,开始调试,顺便在 User中添加一个 构造函数
public User() {
System.out.println("构造函数触发了");
}
跟进readValue:
跟进 _readMapAndClose ,一步一步跳,经过两个不匹配的if,将会跳到此处的 deserialize
进入 deserialize
因为这里的 this._vanillaProcessing 为true 所以他会调用下方的 this._vanillaProcessing
跟进 vanillaDeserialize() ,跟进去,BeanDeserializer.vanillaDeserialize() 函数的实现比较简单,先调用createUsingDefault() 函数来调用指定类的无参构造函数来生成类实例:
进入call ,实例化返回
此时 实例化完之后,User的构造器 已经触发了:
上面那个序列化内容不用看,因为是我忘记注释
String json = mapper.writeValueAsString(user);
BeanDeserializer.vanillaDeserialize() 函数调用完 无参的类构造函数生成 实例Bean(也就是User)后,就开始进入do while 循环来循环解析键值对中的属性值并调用 deserializeAndSet() 函数来解析并设置Bean 的属性值
跟进去 SettableBeanProperty.deserialize()函数,可以看到有两个反序列化的代码逻辑,其中if判断语句会判断当前反序列化的内容是否携带类型,若是则调用deserializeWithType()函数解析,否则直接调用deserialize()函数解析:
我们的第一个类型是username 也就是string 就会跳到下面的调用,相反如果你的值是int 则会进入第一个if。我们进入的是 StringDeserializer.deserialize()
跟进deserialize()
后,用了p.getText()
,获取到了p的属性值
一路跳过,最终返回到前面,通过invoke 命令 调用到了 username的setter方法
password属性也是String类型的,所以通过前边的do while循环,在调用deserializeAndSet();
获取对应的值,跟username过程完全一样不看了 。
至此,整个函数调用过程大致过了一遍。使用@JsonTypeInfo注解的函数调用过程也是一样的。
简单梳理一遍,Jackson反序列化的过程为,先调用通过无参的构造函数生成目标类实例,接着是根据属性值是否是数组的形式即是否带类名来分别调用不同的函数来设置实例的属性值,其中会调用Object类型属性的构造函数和setter方法。
总结:
在Jackson反序列化中,若调用了enableDefaultTyping()函数或使用@JsonTypeInfo注解指定反序列化得到的类的属性为JsonTypeInfo.Id.CLASS或JsonTypeInfo.Id.MINIMAL_CLASS,则会调用该属性的类的构造函数和setter方法。
反序列化漏洞
前提条件
满足下面三点之一 即存在Jackson 反序列化漏洞:
- 调用了 ObjectMapper.enableDefaultTyping()函数
- 对要进行反序列化的类的属性使用了值为JsonTypeInfo.Id.CLASS的@JsonTypeInfo注解;
- 对要进行反序列化的类的属性使用了值为JsonTypeInfo.Id.MINIMAL_CLASS的@JsonTypeInfo注解;
漏洞原理
由之前的结论知道,当使用的JacksonPolymorphicDeserialization机制配置有问题时,Jackson反序列化就会调用属性所属类的构造函数和setter方法。而如果该构造函数或setter方法存在危险操作,那么就存在Jackson反序列化漏洞
DEMO
package JACKSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JacksonTest {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
String json = "{\"age\":17,\"name\":\"snowy\",\"sex\":[\"JACKSON.MySex\",{\"sex\":1}]}";
Person p2 = mapper.readValue(json, Person.class);
System.out.println(p2);
}
}
package JACKSON;
public class MySex implements Sex {
int sex;
public MySex() {
System.out.println("MySex构造函数");
}
@Override
public int getSex() {
System.out.println("MySex.getSex");
return sex;
}
@Override
public void setSex(int sex) {
System.out.println("MySex.setSex");
this.sex = sex;
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}
}
package JACKSON;
public class Person {
public int age;
public String name;
// @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
public Sex sex;
public Person() {
System.out.println("Person构造函数");
}
@Override
public String toString() {
return String.format("Person.age=%d, Person.name=%s, %s", age, name, sex == null ? "null" : sex);
}
}
package JACKSON;
public interface Sex {
public void setSex(int sex);
public int getSex();
}
参考文章
Jackson系列一——反序列化漏洞基本原理 [ Mi1k7ea ]