Jackson反序列化安全研究
1. Jackson简介
Jackson是一个开源的Java序列化和反序列化工具,可以将Java对象序列化为XML或JSON格式的字符串,以及将XML或JSON格式的字符串反序列化为Java对象。由于其使用简单、速度较快且不依赖除JDK外的其他库,被众多用户所使用。
1.1 基本依赖
<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>
2. Jackson基本使用
2.1 序列化与反序列化
Jackson提供了ObjectMapper.writeValueAsString()和ObjectMapper.readValue()两个方法来实现序列化和反序列化的功能。
示例POJO类
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;
}
// getter和setter方法
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; }
}
测试代码
public class JacksonTest {
public static void main(String[] args) throws IOException {
User user = new User("Sentiment", "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);
}
}
输出结果
{"username":"Sentiment","password":"123456"}
Jackson.User@67b6d4ae
3. Jackson多态处理
Jackson实现了JacksonPolymorphicDeserialization机制来解决多态问题,有两种方法:DefaultTyping和@JsonTypeInfo注解。
3.1 DefaultTyping
Jackson提供enableDefaultTyping设置,包含4个值:
JAVA_LANG_OBJECTOBJECT_AND_NON_CONCRETE(默认)NON_CONCRETE_AND_ARRAYSNON_FINAL
3.1.1 JAVA_LANG_OBJECT
当被序列化或反序列化的类里的属性被声明为Object类型时,会对该Object类型的属性进行序列化和反序列化,并明确记录类名。
示例代码:
public class User2 {
public String name = "Tana";
}
// 在User类中添加Object属性
private Object object;
// 测试代码
public class JacksonTest {
public static void main(String[] args) throws IOException {
User user = new User("Sentiment", "123456", 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):
{"username":"Sentiment","password":"123456","object":["Jackson.User2",{"name":"Tana"}]}
User{username='Sentiment', password='123456', object=Jackson.User2@1753acfe}
输出结果(不设置JAVA_LANG_OBJECT):
{"username":"Sentiment","password":"123456","object":{"name":"Tana"}}
User{username='Sentiment', password='123456', object={name=Tana}}
3.1.2 其他DefaultTyping类型
| DefaultTyping类型 | 描述说明 |
|---|---|
| JAVA_LANG_OBJECT | 属性的类型为Object |
| OBJECT_AND_NON_CONCRETE | 属性的类型为Object、Interface、AbstractClass |
| NON_CONCRETE_AND_ARRAYS | 属性的类型为Object、Interface、AbstractClass、Array |
| NON_FINAL | 所有除了声明为final之外的属性 |
3.2 @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)
3.2.1 JsonTypeInfo.Id.CLASS
public class User {
private String username;
private String password;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
private Object object;
// ...
}
输出结果:
{"username":"Sentiment","password":"123456","object":{"@class":"Jackson.User2","name":"Tana"}}
User{username='Sentiment', password='123456', object=Jackson.User2@5b275dab}
3.2.2 JsonTypeInfo.Id.MINIMAL_CLASS
{"username":"Sentiment","password":"123456","object":{"@c":"Jackson.User2","name":"Tana"}}
User{username='Sentiment', password='123456', object=Jackson.User2@29774679}
3.2.3 JsonTypeInfo.Id.NAME
类似于fastjson的@type字段,但会抛出异常,不能进行反序列化:
{"username":"Sentiment","password":"123456","object":{"@type":"User2","name":"Tana"}}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Could not resolve ...
3.2.4 JsonTypeInfo.Id.CUSTOM
直接抛出异常,需要用户自定义来使用。
4. 反序列化流程分析
Jackson反序列化时会调用对应类的setter和无参构造器。对于Object类型的属性,会分别调用外层类和内层类的无参构造。
4.1 关键流程
- 调用
vanillaDeserialize()方法 - 通过
createUsingDefault()函数调用指定类的无参构造函数生成类实例 - 进入do-while循环解析键值对中的属性值
- 调用
deserializeAndSet()函数解析并设置Bean的属性值 - 对于Object类型属性,会判断反序列化内容是否携带类型信息
- 根据类型信息实例化对应类并设置属性
5. Jackson反序列化漏洞
5.1 漏洞前提条件
满足以下任一条件即存在Jackson反序列化漏洞:
- 调用了
ObjectMapper.enableDefaultTyping()函数 - 对要进行反序列化的类的属性使用了值为
JsonTypeInfo.Id.CLASS的@JsonTypeInfo注解 - 对要进行反序列化的类的属性使用了值为
JsonTypeInfo.Id.MINIMAL_CLASS的@JsonTypeInfo注解
5.2 漏洞原理
Jackson在进行反序列化时会执行构造器和setter方法,如果这些方法中包含恶意代码,就会导致恶意代码执行。
恶意代码示例:
public class User2 {
public String name = "Tana";
public User2() {
System.out.println("User2无参构造");
}
public void setName(String name) {
System.out.println("User2.setName");
this.name = name;
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}
}
当反序列化包含User2实例的JSON数据时,会触发计算器程序的执行。
6. 安全建议
- 避免使用
enableDefaultTyping()方法 - 避免使用
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)或@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS) - 对不可信的JSON输入进行严格过滤
- 及时更新Jackson到最新版本,修复已知漏洞
- 使用白名单机制限制可反序列化的类
7. 总结
Jackson反序列化漏洞的核心原理是在反序列化过程中执行了构造器和setter方法,如果这些方法中包含恶意代码就会导致安全问题。实际攻击中通常会结合特定的调用链(gadget chains)来实现更复杂的攻击。