Jackson 3.x 这两天也发了补丁版:3.1.8 和 3.2.4。修的是和 2.x 同一批带 CVE 编号的问题,3.2.4 还多一个。Spring Boot 4.1.1 默认管理的 Jackson 是 3.1.5,不含这批修复。我用同一个接口实测,3.1.5 和 3.1.7 会把白名单外的类从泛型类型 id 里实例化出来,3.1.8 会拒绝。

升到 3.2.4 还有个坑。只改 jackson-bom.version 的话,一反序列化就抛 NoSuchMethodError。要把 jackson-2-bom.version 一起改到 2.22.4 才行。

按 Jackson wiki 的发布说明,3.1.8 的发布日期是 10 月 17 日,3.2.4 是 10 月 18 日。jackson-databind 仓库里两个 tag 的时间换算成北京时间,分别是 10 月 18 日 8 点 16 分和 10 月 19 日 6 点 44 分。2.x 的 2.22.4、2.21.8、2.18.12 在 10 月 17 日就先发了。

3.x 这次修了哪些 CVE

databind 里是 #6287,CVE-2026-110879,也就是下面要实测的泛型类型 id 校验。其余几条在二进制格式模块里,都是长度校验和内存分配方面的修复:Avro #819(CVE-2026-104015)、CBOR #821(CVE-2026-106554)、Protobuf #817(CVE-2026-104016、CVE-2026-104017、CVE-2026-110747)、Smile #825(CVE-2026-104895)。这七个编号 3.1.8 和 3.2.4 都有,和 2.22.4、2.21.8 一致。

3.x 独有的是 Streaming 里的 #1765:非阻塞解析器统一执行十六进制数字的长度检查,编号 CVE-2026-110729。它只出现在 3.2.4 的说明里,3.1.8 和 2.x 三个补丁版都没列。

发布说明只给了编号,没有严重级别。我在北京时间 10 月 19 日早上 8 点 21 分查了这八个编号的状态。下面是其中一个的查询结果,其余七个一样:

$ curl -s https://cveawg.mitre.org/api/cve-id/CVE-2026-110879
{"cve_id":"CVE-2026-110879","cve_year":"2026","state":"RESERVED","owning_cna":"[REDACTED]"}
$ curl -s 'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2026-110879' | jq .totalResults
0
$ curl -s 'https://api.github.com/advisories?cve_id=CVE-2026-110879' | jq length
0

八个编号在 cve.org 上都是 RESERVED,NVD 查不到,GitHub Advisory Database 里也没有对应的 GHSA。#6287 的 PR 描述里引用了 GHSA-m8xf-2cr7-77ph,这个页面现在还是 404:

$ curl -s -o /dev/null -w '%{http_code}\n' https://github.com/FasterXML/jackson-databind/security/advisories/GHSA-m8xf-2cr7-77ph
404
$ date
Mon Oct 19 08:21:33 CST 2026

所以这里不写严重级别。

CVE-2026-110879 的问题,按 PR #6287 的描述,出在 Id.CLASS 和 Id.MINIMAL_CLASS 这种类名形式的类型 id 上:外层类型过了校验以后,尖括号里的类型参数和数组元素类型就不再经过配置的 PolymorphicTypeValidator。修复后它们要和直接写在类型 id 里的类走一样的检查,PR 里的例子是在 allowIfBaseType(Msg.class) 下 Env<PlainMsg> 能过、Env<Gadget> 不能过。完整的原理和 Spring Boot 3.5 上的复现过程,可以看之前那篇讲 Jackson 2.x 三个补丁版的文章。

Spring Boot 4.1.1 默认带的是 3.1.5

Spring Boot 4.1.1 的 BOM 里,Jackson 3 和 Jackson 2 各有一个版本属性。我顺手也看了 3.1.8 和 3.2.4 的 BOM 各自要哪个 jackson-annotations:

$ curl -s https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/4.1.1/spring-boot-dependencies-4.1.1.pom | grep -E '<jackson(-2)?-bom.version>'
    <jackson-2-bom.version>2.21.5</jackson-2-bom.version>
    <jackson-bom.version>3.1.5</jackson-bom.version>
$ curl -s https://repo1.maven.org/maven2/tools/jackson/jackson-bom/3.1.8/jackson-bom-3.1.8.pom | grep '<jackson.version.annotations>'
    <jackson.version.annotations>2.21</jackson.version.annotations>
$ curl -s https://repo1.maven.org/maven2/tools/jackson/jackson-bom/3.2.4/jackson-bom-3.2.4.pom | grep '<jackson.version.annotations>'
    <jackson.version.annotations>2.22</jackson.version.annotations>

jackson-bom.version 管 Jackson 3(tools.jackson),jackson-2-bom.version 管 Jackson 2(com.fasterxml.jackson)。Jackson 3 没有自己的注解包,继续用 2.x 的 jackson-annotations。3.1.8 要的是 2.21,和 Boot 4.1.1 自带的一致。3.2.4 要的是 2.22,后面那个 NoSuchMethodError 就是这么来的。Boot 4 换 Jackson 3 的背景可以看之前那篇讲老项目迁到 Jackson 3 的文章。

用 spring-boot-starter-parent 的 Maven 项目,在自己的 pom.xml 里覆盖属性就行。升到 3.1.8 只改一个:

<properties>
    <jackson-bom.version>3.1.8</jackson-bom.version>
</properties>

升到 3.2.4 要改两个:

<properties>
    <jackson-bom.version>3.2.4</jackson-bom.version>
    <jackson-2-bom.version>2.22.4</jackson-2-bom.version>
</properties>

下面的实测为了方便切换版本,用的是命令行 -D 覆盖,效果一样。

在 Spring Boot 4.1.1 里实测

环境是 Java 21.0.12.1,构建用 Maven 3.9.9:

$ java -version
openjdk version "21.0.12.1" 2026-08-18
OpenJDK Runtime Environment (build 21.0.12.1+1-1-deb13u1-Debian)
OpenJDK 64-Bit Server VM (build 21.0.12.1+1-1-deb13u1-Debian, mixed mode, sharing)

项目的父 POM 是 spring-boot-starter-parent 4.1.1,只依赖 spring-boot-starter-webmvc。模型就照 PR 的例子搭:Msg 接口用类名做类型 id,Env<T> 是实现了 Msg 的泛型信封,PlainMsg 是正常消息。Gadget 不实现 Msg,构造器和 setCmd 只打一行日志,用来看它有没有被创建。POST /inbox 收一个只有 Msg msg 字段的 Inbox,返回 payload 的实际类型。

注意 Msg 上的注解还是 com.fasterxml.jackson.annotation 包里的,这就是 Jackson 3 依赖 2.x 注解包的地方:

package com.example.ptvdemo;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public interface Msg {
}

校验器只放行 Msg 的子类型。Boot 4 里用 JsonMapperBuilderCustomizer 直接在 Jackson 3 的 builder 上设置,启动后打印实际加载的 jackson-databind 版本:

package com.example.ptvdemo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;

import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;

@SpringBootApplication
public class PtvDemoApplication {

    private static final Logger log = LoggerFactory.getLogger(PtvDemoApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(PtvDemoApplication.class, args);
    }

    @Bean
    JsonMapperBuilderCustomizer onlyMsgSubtypes() {
        BasicPolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
                .allowIfBaseType(Msg.class)
                .build();
        return builder -> builder.polymorphicTypeValidator(ptv);
    }

    @Bean
    ApplicationRunner printJacksonVersion() {
        return args -> log.info("jackson-databind version: {}",
                tools.jackson.databind.cfg.PackageVersion.VERSION);
    }
}

端口是 server.port=18081。请求体有三个,env-plain.json 是正常的 Env<PlainMsg>,env-gadget.json 把类型参数换成 Gadget,direct-gadget.json 直接拿 Gadget 当类型 id:

{"msg":{"@class":"com.example.ptvdemo.Env<com.example.ptvdemo.PlainMsg>","payload":{"@class":"com.example.ptvdemo.PlainMsg","text":"hi"}}}
{"msg":{"@class":"com.example.ptvdemo.Env<com.example.ptvdemo.Gadget>","payload":{"cmd":"touch-file"}}}
{"msg":{"@class":"com.example.ptvdemo.Gadget","cmd":"touch-file"}}

同一份代码分别用 Boot 默认版本、3.1.7、3.1.8 打包运行,打完包看 jar 里实际带的 databind(摘录):

$ mvn -B clean package -DskipTests -Djackson-bom.version=3.1.7
[INFO] BUILD SUCCESS
$ jar tf target/jackson3-ptv-demo-0.0.1.jar | grep jackson-databind
BOOT-INF/lib/jackson-databind-3.1.7.jar

$ mvn -B clean package -DskipTests -Djackson-bom.version=3.1.8
[INFO] BUILD SUCCESS
$ jar tf target/jackson3-ptv-demo-0.0.1.jar | grep jackson-databind
BOOT-INF/lib/jackson-databind-3.1.8.jar

默认那次不带 -Djackson-bom.version,jar 里是 jackson-databind-3.1.5.jar。

3.1.5 和 3.1.7:Gadget 被创建了

Boot 默认的 3.1.5 下,正常消息和泛型里塞 Gadget 的请求都返回 200:

$ curl -s -w '\nHTTP %{http_code}\n' -H 'Content-Type: application/json' -d @requests/env-plain.json localhost:18081/inbox
accepted Env, payload type = com.example.ptvdemo.PlainMsg

HTTP 200

$ curl -s -w '\nHTTP %{http_code}\n' -H 'Content-Type: application/json' -d @requests/env-gadget.json localhost:18081/inbox
accepted Env, payload type = com.example.ptvdemo.Gadget

HTTP 200

应用日志里 Gadget 的构造器和 setCmd 都执行了。直接写 Gadget 的第三个请求被拦下,原因是 Not a subtype(摘录):

2026-10-19T08:20:21.799+08:00  INFO 254658 --- [           main] com.example.ptvdemo.PtvDemoApplication   : jackson-databind version: 3.1.5
2026-10-19T08:20:22.127+08:00  WARN 254658 --- [io-18081-exec-2] com.example.ptvdemo.Gadget               : Gadget constructor called
2026-10-19T08:20:22.127+08:00  WARN 254658 --- [io-18081-exec-2] com.example.ptvdemo.Gadget               : Gadget.setCmd called with: touch-file
2026-10-19T08:20:22.142+08:00  WARN 254658 --- [io-18081-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Could not resolve type id 'com.example.ptvdemo.Gadget' as a subtype of `com.example.ptvdemo.Msg`: Not a subtype]

3.1.7 的结果一样(摘录):

2026-10-19T08:20:31.723+08:00  INFO 255216 --- [           main] com.example.ptvdemo.PtvDemoApplication   : jackson-databind version: 3.1.7
2026-10-19T08:20:32.316+08:00  WARN 255216 --- [io-18081-exec-4] com.example.ptvdemo.Gadget               : Gadget constructor called
2026-10-19T08:20:32.316+08:00  WARN 255216 --- [io-18081-exec-4] com.example.ptvdemo.Gadget               : Gadget.setCmd called with: touch-file

3.1.8:泛型参数里的 Gadget 被拒绝

同样的请求,正常消息照常返回 200,带 Gadget 的变成 400:

$ curl -s -w '\nHTTP %{http_code}\n' -H 'Content-Type: application/json' -d @requests/env-plain.json localhost:18081/inbox
accepted Env, payload type = com.example.ptvdemo.PlainMsg

HTTP 200

$ curl -s -w '\nHTTP %{http_code}\n' -H 'Content-Type: application/json' -d @requests/env-gadget.json localhost:18081/inbox
{"timestamp":"2026-10-19T00:20:37.385Z","status":400,"error":"Bad Request","path":"/inbox"}
HTTP 400

日志里没有 Gadget constructor called,异常信息写明是配置的校验器拒绝了 contained type(摘录):

2026-10-19T08:20:37.050+08:00  INFO 255463 --- [           main] com.example.ptvdemo.PtvDemoApplication   : jackson-databind version: 3.1.8
2026-10-19T08:20:37.376+08:00  WARN 255463 --- [io-18081-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Could not resolve type id 'com.example.ptvdemo.Env<com.example.ptvdemo.Gadget>' as a subtype of `com.example.ptvdemo.Msg`: Configured `PolymorphicTypeValidator` (of type `tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator`) denied resolution of contained type `com.example.ptvdemo.Gadget`]
2026-10-19T08:20:37.413+08:00  WARN 255463 --- [io-18081-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Could not resolve type id 'com.example.ptvdemo.Gadget' as a subtype of `com.example.ptvdemo.Msg`: Not a subtype]

400 的响应体是 Spring Boot 默认的错误 JSON,不带具体原因,原因只在服务端日志里。

只改 jackson-bom.version 到 3.2.4:三个请求全是 500

只覆盖 jackson-bom.version=3.2.4 时,打包和启动都正常,三个请求却全部返回 500。第一个请求的结果:

$ curl -s -w '\nHTTP %{http_code}\n' -H 'Content-Type: application/json' -d @requests/env-plain.json localhost:18081/inbox
{"timestamp":"2026-10-19T00:20:43.513Z","status":500,"error":"Internal Server Error","path":"/inbox"}
HTTP 500

应用日志里是 NoSuchMethodError(摘录):

2026-10-19T08:20:43.050+08:00  INFO 255857 --- [           main] com.example.ptvdemo.PtvDemoApplication   : jackson-databind version: 3.2.4
java.lang.NoSuchMethodError: 'java.lang.Boolean com.fasterxml.jackson.annotation.JsonTypeInfo$Value.getWriteTypeIdForDefaultImpl()'
	at tools.jackson.databind.jsontype.impl.StdTypeResolverBuilder.withSettings(StdTypeResolverBuilder.java:305) ~[jackson-databind-3.2.4.jar!/:3.2.4]

看 jar 里的依赖,databind 和 core 是 3.2.4,注解包还停在 2.21:

$ jar tf target/jackson3-ptv-demo-0.0.1.jar | grep -E 'jackson-(annotations|core|databind)'
BOOT-INF/lib/jackson-databind-3.2.4.jar
BOOT-INF/lib/jackson-annotations-2.21.jar
BOOT-INF/lib/jackson-core-3.2.4.jar

Boot 4.1.1 同时导入了 Jackson 2 和 Jackson 3 两个 BOM,jackson-annotations 最后用的是 Jackson 2 那边(jackson-2-bom.version,2.21.5)给的 2.21。单改 jackson-bom.version 不会带上它。把两个属性一起改掉再打包(摘录):

$ mvn -B clean package -DskipTests -Djackson-bom.version=3.2.4 -Djackson-2-bom.version=2.22.4
[INFO] BUILD SUCCESS
$ jar tf target/jackson3-ptv-demo-0.0.1.jar | grep -E 'jackson-(annotations|core|databind)'
BOOT-INF/lib/jackson-databind-3.2.4.jar
BOOT-INF/lib/jackson-annotations-2.22.jar
BOOT-INF/lib/jackson-core-3.2.4.jar

这回注解包是 2.22,三个请求的结果和 3.1.8 一样:

$ curl -s -w '\nHTTP %{http_code}\n' -H 'Content-Type: application/json' -d @requests/env-plain.json localhost:18081/inbox
accepted Env, payload type = com.example.ptvdemo.PlainMsg

HTTP 200

$ curl -s -w '\nHTTP %{http_code}\n' -H 'Content-Type: application/json' -d @requests/env-gadget.json localhost:18081/inbox
{"timestamp":"2026-10-19T00:21:08.192Z","status":400,"error":"Bad Request","path":"/inbox"}
HTTP 400

日志里拒绝的原因也一样(摘录):

2026-10-19T08:21:07.584+08:00  INFO 256977 --- [           main] com.example.ptvdemo.PtvDemoApplication   : jackson-databind version: 3.2.4
2026-10-19T08:21:08.185+08:00  WARN 256977 --- [io-18081-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Could not resolve type id 'com.example.ptvdemo.Env<com.example.ptvdemo.Gadget>' as a subtype of `com.example.ptvdemo.Msg`: Configured `PolymorphicTypeValidator` (of type `tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator`) denied resolution of contained type `com.example.ptvdemo.Gadget`]

这个 500 我是在带 @JsonTypeInfo 的接口上碰到的,3.2.4 在 Boot 4.1.1 上没有跑别的场景。

项目信息

Jackson 用的是 Apache License 2.0。databind 仓库在 github.com/FasterXML/jackson-databind,Avro、CBOR、Protobuf、Smile 几个二进制格式模块在 github.com/FasterXML/jackson-dataformats-binary。修复清单以 3.1.8 和 3.2.4 的 wiki 发布说明为准,泛型类型 id 的细节来自 PR #6287 的描述。

— 感谢阅读 —
广告

一起交流

分享你的思考,让讨论更进一步。