Apache Camel 4.22.2 是 2026-10-22 发布的 LTS 补丁,一共 86 项修复。其中 CVE-2026-90885(Camel 安全页标 High)针对 camel-saxon:消息体已经是 javax.xml.transform.Source 时,XQuery 会把这份 Source 直接交给 Saxon 解析。用 Spring Boot 4.1.1 起两个独立进程对照,4.22.1 对带 DOCTYPE 的请求回 200,正文是本地 canary 文件内容;4.22.2 回 400,报 DOCTYPE is disallowed。没有 DOCTYPE 的正常 XML,4.22.2 仍然回 hello-world。
官方主源是 RELEASE 4.22.2、release notes,以及 camel.apache.org/security 上对 CVE-2026-90885 的条目。修复提交是 9c23b6868bb92cb19c2b5a7ee624421d760cda8f(CAMEL-24691)。截至写稿时,cve.org 上 CVE-2026-90885 仍是 Reserved;严重级别按 Camel 安全页写的 High,不另编。
4.22.2 相对 4.22.1 多修了哪些 CVE
10 月 16 日那篇「38 个 advisory」覆盖的是 4.18.5 / 4.22.1 / 4.23.0。4.22.2 又补了一批,安全页上 Fixed 列含 4.22.2、且影响区间写到 before 4.22.2 的有这些:
| CVE | Camel 安全页级别 | 组件 | 一句话 |
|---|---|---|---|
| CVE-2026-90885 | High | camel-saxon | 已是 Source 的消息体走 XQuery 时,Saxon 默认解析会解析外部实体 |
| CVE-2026-95664 | Medium | camel-aws2-s3 | 对象 key 头被当成 Simple 表达式求值 |
| CVE-2026-95663 | Medium | camel-keycloak | token 自省缓存不受 token 过期约束 |
| CVE-2026-95629 | Medium | camel-docling | 输出路径头未校验,可写到任意目录 |
| CVE-2026-95628 | Medium | camel-docling | 自定义 CLI 参数仍接受绝对路径 |
| CVE-2026-94386 | Medium | camel-oauth | 客户端凭证 token 缓存键过粗,跨 scope 复用 |
| CVE-2026-101259 | Medium | camel-coap | 构造响应失败时把异常消息回给 CoAP 客户端 |
下面只实测 High 的那条。String / byte[] / InputStream 进 XQuery 时本来就会先走 Camel 已加固的 SAX/StAX 转换;问题出在消息体已经是 Source 的路径,例如路由里主动包成 StringSource。
验证项目
环境是 JDK 21.0.12.1、Maven 3.9.9、Spring Boot 4.1.1。Camel 版本用 -Dcamel.version 分别打 4.22.1 和 4.22.2 两个 fat jar,各起一个进程,端口 18081 和 18082。
$ 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)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-bom</artifactId>
<version>${camel.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-servlet-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-saxon-starter</artifactId>
</dependency>
</dependencies>
$ mvn -q -Dcamel.version=4.22.1 clean package -DskipTests
$ mvn -q -Dcamel.version=4.22.2 clean package -DskipTests
$ jar tf jars/camel-saxon-xxe-demo-4.22.1.jar | grep camel-saxon-4
BOOT-INF/lib/camel-saxon-4.22.1.jar
$ jar tf jars/camel-saxon-xxe-demo-4.22.2.jar | grep camel-saxon-4
BOOT-INF/lib/camel-saxon-4.22.2.jar
路由先把 HTTP 体包成 StringSource,再跑 XQuery。这样走的就是 CVE 描述的那条路径:
from("servlet:/xquery?httpMethodRestrict=POST&matchOnUriPrefix=false")
.routeId("xquery-source")
.process(exchange -> {
String xml = exchange.getMessage().getBody(String.class);
exchange.getMessage().setBody(new StringSource(xml));
})
.transform().xquery("//order", String.class)
.setHeader(Exchange.CONTENT_TYPE, constant("text/plain;charset=UTF-8"));
异常统一回 400,正文写异常类名和 message,方便对照。启动日志摘录:
2026-10-23T08:06:36.603+08:00 INFO 1247271 --- [camel-saxon-xxe-demo] [ main] o.a.c.impl.engine.AbstractCamelContext : Apache Camel 4.22.1 (camel-1) is starting
2026-10-23T08:06:36.617+08:00 INFO 1247271 --- [camel-saxon-xxe-demo] [ main] o.a.c.impl.engine.AbstractCamelContext : Started xquery-source (servlet:/xquery)
2026-10-23T08:06:36.701+08:00 INFO 1247270 --- [camel-saxon-xxe-demo] [ main] o.a.c.impl.engine.AbstractCamelContext : Apache Camel 4.22.2 (camel-1) is starting
2026-10-23T08:06:36.714+08:00 INFO 1247270 --- [camel-saxon-xxe-demo] [ main] o.a.c.impl.engine.AbstractCamelContext : Started xquery-source (servlet:/xquery)
$ java -jar jars/camel-saxon-xxe-demo-4.22.1.jar --server.port=18081
$ java -jar jars/camel-saxon-xxe-demo-4.22.2.jar --server.port=18082
对照请求
canary 文件内容是 CANARY-XQUERY-XXE-do-not-disclose。带 DOCTYPE 的请求体如下:
<?xml version="1.0"?>
<!DOCTYPE data [ <!ENTITY xxe SYSTEM "file:///tmp/camel-xxe-canary.txt"> ]>
<order status="pending">&xxe;</order>
摘录,4.22.1(端口 18081)回 200,正文就是 canary:
$ curl -s -i -X POST http://127.0.0.1:18081/xquery -H 'Content-Type: application/xml' --data-binary @logs/payload-xxe.xml
HTTP/1.1 200
User-Agent: curl/8.14.1
Accept: */*
Content-Type: text/plain;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 23 Oct 2026 00:08:25 GMT
CANARY-XQUERY-XXE-do-not-disclose
摘录,4.22.2(端口 18082)回 400,DOCTYPE 被关掉:
$ curl -s -i -X POST http://127.0.0.1:18082/xquery -H 'Content-Type: application/xml' --data-binary @logs/payload-xxe.xml
HTTP/1.1 400
User-Agent: curl/8.14.1
Accept: */*
Content-Type: text/plain;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 23 Oct 2026 00:08:25 GMT
Connection: close
org.apache.camel.RuntimeExpressionException: net.sf.saxon.trans.XPathException: SXXP0003 Error reported by XML parser: DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.
摘录,同一 4.22.2 进程上,没有 DOCTYPE 的请求仍正常:
$ curl -s -i -X POST http://127.0.0.1:18082/xquery -H 'Content-Type: application/xml' --data-binary @logs/payload-benign.xml
HTTP/1.1 200
User-Agent: curl/8.14.1
Accept: */*
Content-Type: text/plain;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 23 Oct 2026 00:08:25 GMT
hello-world
提交 9c23b6868bb92cb19c2b5a7ee624421d760cda8f 给默认 Saxon Configuration 加了 disallow-doctype-decl,并关掉外部 general / parameter entity 和外部 DTD 加载。用户自己注入的 Configuration(配置项或 XQueryBuilder.setConfiguration)按原样使用,这次没测那条路径。
项目信息
Apache Camel 许可证是 Apache License 2.0,仓库在 github.com/apache/camel。4.22.x 与 Spring Boot 4.1.1 对齐(Camel parent 里的 spring-boot-version)。还在 4.22.1 的,至少把 camel-saxon 升到 4.22.2;也可以跟 4.18.5 或 4.23.0,这三条线都带了同一处 Saxon 加固。
一起交流
分享你的思考,让讨论更进一步。