针对 Apache Camel 漏洞的可运行概念验证复现程序,其中 WebhookUrlValidator
对推送通知 webhook 主机的分类在两个不一致的位置进行:
fc00::/7("fc"、"fd");isLoopbackAddress、
isAnyLocalAddress、isLinkLocalAddress 和 isSiteLocalAddress 进行分类。这些谓词覆盖的范围并不相同:isSiteLocalAddress 报告的是已弃用的 fec0::/10
块,而不是取代它的 fc00::/7 唯一本地地址,并且 JDK 根本没有针对它们的谓词。
因此,同一个地址会因书写方式不同而被接受或拒绝。
| 运行时 | 目录 | 技术栈 |
|---|---|---|
| Camel Spring Boot | camel-spring-boot/ | Spring Boot 3.5.13 + camel-a2a 4.22.1 |
| Camel Quarkus | camel-quarkus/ | Camel Quarkus 3.39.0(Quarkus 3.39.0,Camel 4.22.0) |
主机名必须解析到 fc00::/7,compose 文件通过 extra_hosts 条目来实现这一点
(Docker 在启动时会将其写入容器的 /etc/hosts)——因此请通过 Docker 运行,而不是使用
裸 java -jar:
cd camel-spring-boot # or: cd camel-quarkus
mvn clean package
docker compose up -d --build
until curl -sf http://localhost:8080/exploit/ready >/dev/null; do sleep 1; done
curl -s http://localhost:8080/exploit/attack
docker compose down
在受影响的构建上输出(两个运行时上完全相同):
Resolution in this environment: internal-service.example -> fd00:0:0:0:0:0:0:1
1) The unique local address as an IP LITERAL
url: https://[fd00::1]/push-notification
verdict: REFUSED - Webhook URL must not point to private/internal IPv6 ranges (SSRF protection): [fd00::1]
2) The SAME address reached through a host name
url: https://internal-service.example/push-notification
verdict: ACCEPTED, resolved to fd00:0:0:0:0:0:0:1
3) A legitimate host name that merely BEGINS with "fc" (the bug's other direction)
url: https://fcm.googleapis.com/push-notification
verdict: REFUSED - Webhook URL must not point to private/internal IPv6 ranges (SSRF protection): fcm.googleapis.com
4) CONTROL - an IPv4 private address, which isSiteLocalAddress does cover
verdict: REFUSED - ... site-local/private address ...
5) CONTROL - a link-local IPv6 literal
verdict: REFUSED - ... private/internal IPv6 ranges ...
>>> PROVEN: ...
探针 1 和探针 2 指向同一个地址,但判定结果却不一致。探针 3 是同一不一致性在相反方向上的体现:字符串预检查在未先确认该主机是 IP 字面量的情况下就匹配了原始主机名,因此任何仅仅以 fc 或 fd 开头的名称都会被直接拒绝——其中包括 fcm.googleapis.com 这个完全普通的推送端点。探针 4 和探针 5 是对照组,表明在编写正确的地方该防护是有效的。
每个探针都传入 allowLocal=false,这是默认值(allowLocalWebhookUrls=false),也是预期该防护生效的配置。
Webhook URL 以 TaskPushNotificationConfig.url 的形式来自注册推送通知的调用方,因此它是攻击者可控的。随后 PushNotificationDispatcher 会把推送通知发送到该地址,并附带同一配置中携带的任何 Authorization 头——其方案和凭据均由注册的调用方提供。
响应不会回传给该调用方,因此这产生的是从 Camel 进程发往内部地址的一次盲出站 HTTPS 请求,而该内部地址本应被防护拒绝。以这种方式访问某个服务还需要:该名称在目标环境中解析到 fc00::/7,该地址上有 HTTPS 服务在监听,并且针对所请求名称的 TLS 握手能够成功——这也是严重性被评定为中危的原因。
| 属性 | 值 |
|---|---|
| 组件 | camel-a2a(WebhookUrlValidator) |
| CWE | CWE-918(服务端请求伪造) |
| 严重性 | 中危 |
| 攻击向量 | 推送通知 webhook URL 的主机名解析到 fc00::/7 |
| 影响 | 携带所配置的 Authorization 头,向本应被防护拒绝的内部地址发起一次盲出站 HTTPS 请求 |
| 受影响版本 | 从 4.21.0 起,早于 4.22.2 |
| 修复版本 | 4.22.2、4.23.0 |
| JIRA | CAMEL-24371 |
| 致谢 | 2122295973 at qq dot com,通过 ASF 安全团队报告 |
公告:https://camel.apache.org/security/CVE-2026-71569.html
现在两条路径都经过 InetAddress.getByName,它会解析带方括号的 IPv6 字面量而不触碰 DNS,并使用一个共享的分类器根据原始字节对得到的地址进行分类——因此主机是按其表示的地址来判定,而不是按其书写形式来判定。该分类器能识别 fc00::/7 和共享地址空间 100.64.0.0/10,以及内嵌 IPv4 地址不可全局路由的 IPv4 兼容地址、NAT64(64:ff9b::/96)和 6to4(2002::/16)地址。
已在 main 上通过 apache/camel#25406(54041cce)修复,并回移植到 camel-4.22.x,见 #26583(c4a5c5fc)。
将 camel.version 设置为 4.22.2 并再次运行。已针对 4.22.1 → 4.22.2 验证:
1) The unique local address as an IP LITERAL
verdict: REFUSED - Webhook URL must not point to a unique local address (SSRF protection): [fd00::1]
2) The SAME address reached through a host name
verdict: REFUSED - Webhook URL must not point to a unique local address (SSRF protection): internal-service.example
3) A legitimate host name that merely BEGINS with "fc"
verdict: ACCEPTED, resolved to 216.239.38.55
两种拼写现在都会得到相同的判定结果,并附带相同的消息,而且探针 3 中的误报也消失了。
在 A2A 推送通知配置路径前放置身份验证,使 webhook URL 不会来自不受信任的调用方,并在网络层面拒绝 Camel 进程向 fc00::/7 的出站流量。如果部署不需要 IPv6,使用 -Djava.net.preferIPv4Stack=true 启动 JVM 可以移除该缺口所依赖的地址类别。请注意,allowLocalWebhookUrls=false 已经是默认值,并且它本身并不能关闭该缺口——上面的探针 2 就是在启用它的情况下运行的。
本仓库出于教育和防御目的发布:帮助 Apache Camel 用户理解该漏洞、验证自己是否受到影响,并确认升级可以解决该问题。请勿将本材料用于您不拥有或运营的系统。