ApacheCamel远程代码执行漏洞|CVE-2026-40453复现&研究

admin 2026-05-06 05:42:58 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档分析了ApacheCamel框架中的远程代码执行漏洞CVE-2026-40453,指出受影响版本中多个HeaderFilterStrategy实现因大小写敏感过滤与内部大小写不敏感存储不匹配,导致攻击者可通过变体header绕过过滤,利用camel-exec等组件执行恶意命令或任意文件写入。文档提供了基于Ubuntu24的环境搭建脚本和Docker复现步骤,并说明修复方案是在策略类构造函数中添加setLowerCase(true)确保统一小写过滤。 综合评分: 85 文章分类: 漏洞分析,漏洞预警,应急响应,WEB安全,应用安全


cover_image

Apache Camel 远程代码执行漏洞 | CVE-2026-40453复现&研究

原创

404号浪漫 404号浪漫

404号浪漫

2026年5月4日 22:07 北京

在小说阅读器读本章

去阅读

点击蓝字,关注我们

0x0 背景介绍

Apache Camel是一个开源的集成框架,基于已知的企业集成模式实现。

受影响版本中,JmsHeaderFilterStrategy、ClassicJmsHeaderStrategy、SjmsHeaderFilterStrategy、CoAPHeaderFilterStrategy和GooglePubsubHeaderFilterStrategy这5个非HTTP HeaderFilterStrategy实现使用大小写敏感的String.startsWith进行header过滤,而Camel Exchange内部以大小写不敏感map存储header。攻击者可通过JMS等协议注入大小写变体的Camel内部header(如’CAmelExecCommandExecutable’),绕过过滤后被camel-exec、camel-file等下游组件以标准大小写处理,导致远程代码执行或任意文件写入。

修复版本中通过在这5个策略类的构造函数中添加setLowerCase(true),使header名称在过滤前先转换为小写,确保大小写变体均可被正确过滤。

漏洞详情

| | | — | | |

| 漏洞类型 | 影响版本 | 利用复杂度 | CVE编号 | | — | — | — | — | | 代码执行 | Apache Camel Google PubSub >= 4.14.6 Apache Camel CoAP >= 4.14.6 Apache Camel JMS >= 4.14.6 | 低 | CVE-2026-40453 |

攻击效果:

  • 执行恶意命令,导致RCE。

0x1 环境搭建(Ubuntu24)

  • 为了复现环境整的,而非标准生产环境
#!/bin/bash# CVE-2026-40453 漏洞环境搭建脚本# 若任何命令失败则立即退出set&nbsp;-eecho&nbsp;"[*] 阶段1/4:检查并安装基础依赖..."if&nbsp;!&nbsp;command&nbsp;-v docker &> /dev/null;&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[+] Docker 未安装,正在尝试安装..."&nbsp; &nbsp;&nbsp;# 这里仅演示安装docker.io,实际生产环境建议使用官方源&nbsp; &nbsp;&nbsp;sudo&nbsp;apt update &&&nbsp;sudo&nbsp;apt install -y docker.iofisudo&nbsp;systemctl&nbsp;enable&nbsp;--now docker 2>/dev/null ||&nbsp;true# 检查 Docker Compose 插件(现代标准)if&nbsp;! docker compose version &> /dev/null;&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[!] 警告:Docker Compose 插件未安装。请安装 docker-compose-plugin 或使用 'docker-compose' 命令。"&nbsp; &nbsp;&nbsp;echo&nbsp;" &nbsp; &nbsp;本脚本将尝试使用 'docker-compose' 命令(如果已安装)。"fiecho&nbsp;"[*] 阶段2/4:创建工作目录并生成项目文件..."WORKDIR="$HOME/cve-2026-40453"mkdir&nbsp;-p&nbsp;"$WORKDIR/camel-app/src/main/java/org/vuln"&nbsp;&&&nbsp;cd&nbsp;"$WORKDIR"echo&nbsp;"[+] 进入工作目录:&nbsp;$PWD"# 阶段2.1:生成 docker-compose.ymlecho&nbsp;"[*] 正在生成 docker-compose.yml..."cat&nbsp;> docker-compose.yml <<'EOF'version:&nbsp;'3.8'services:&nbsp; activemq:&nbsp; &nbsp; image: apache/activemq-classic:6.1.6&nbsp; &nbsp; container_name: activemq&nbsp; &nbsp; ports:&nbsp; &nbsp; &nbsp; -&nbsp;"61616:61616"&nbsp; &nbsp; &nbsp;&nbsp;# JMS&nbsp; &nbsp; &nbsp; -&nbsp;"8161:8161"&nbsp; &nbsp; environment:&nbsp; &nbsp; &nbsp; ACTIVEMQ_ADMIN_LOGIN: admin&nbsp; &nbsp; &nbsp; ACTIVEMQ_ADMIN_PASSWORD: admin&nbsp; camel-app:&nbsp; &nbsp; build: ./camel-app&nbsp; &nbsp; container_name: camel-app&nbsp; &nbsp; depends_on:&nbsp; &nbsp; &nbsp; - activemq&nbsp; &nbsp; environment:&nbsp; &nbsp; &nbsp; ACTIVEMQ_BROKER_URL: tcp://activemq:61616&nbsp; &nbsp;&nbsp;# 简单用 sleep 防止 ActiveMQ 未完全就绪,生产可加 healthcheck&nbsp; &nbsp; entrypoint: ["/bin/sh",&nbsp;"-c",&nbsp;"sleep 15 && java -jar /app/app.jar"]EOF# 阶段2.2:生成 Dockerfileecho&nbsp;"[*] 正在生成 camel-app/Dockerfile..."cat&nbsp;> camel-app/Dockerfile <<'EOF'# 编译阶段FROM maven:3.9-eclipse-temurin-17 AS builderWORKDIR /srcCOPY pom.xml .COPY src ./srcRUN mvn package -DskipTests -q# 运行阶段FROM eclipse-temurin:17-jreWORKDIR /appCOPY --from=builder /src/target/*.jar app.jarEXPOSE 8080CMD ["java",&nbsp;"-jar",&nbsp;"app.jar"]EOF# 阶段2.3:生成 pom.xmlecho&nbsp;"[*] 正在生成 camel-app/pom.xml..."cat&nbsp;> camel-app/pom.xml <<'EOF'<project xmlns="http://maven.apache.org/POM/4.0.0"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xsi:schemaLocation="http://maven.apache.org/POM/4.0.0&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;http://maven.apache.org/xsd/maven-4.0.0.xsd">&nbsp; <modelVersion>4.0.0</modelVersion>&nbsp; <parent>&nbsp; &nbsp; <groupId>org.springframework.boot</groupId>&nbsp; &nbsp; <artifactId>spring-boot-starter-parent</artifactId>&nbsp; &nbsp; <version>3.3.5</version>&nbsp; &nbsp; <relativePath/>&nbsp; </parent>&nbsp; <groupId>org.vuln</groupId>&nbsp; <artifactId>camel-cve-2026-40453</artifactId>&nbsp; <version>1.0.0</version>&nbsp; <properties>&nbsp; &nbsp; <java.version>17</java.version>&nbsp; &nbsp; <camel.version>4.14.5</camel.version>&nbsp; </properties>&nbsp; <dependencyManagement>&nbsp; &nbsp; <dependencies>&nbsp; &nbsp; &nbsp; <dependency>&nbsp; &nbsp; &nbsp; &nbsp; <groupId>org.apache.camel.springboot</groupId>&nbsp; &nbsp; &nbsp; &nbsp; <artifactId>camel-spring-boot-bom</artifactId>&nbsp; &nbsp; &nbsp; &nbsp; <version>${camel.version}</version>&nbsp; &nbsp; &nbsp; &nbsp; <type>pom</type>&nbsp; &nbsp; &nbsp; &nbsp; <scope>import</scope>&nbsp; &nbsp; &nbsp; </dependency>&nbsp; &nbsp; </dependencies>&nbsp; </dependencyManagement>&nbsp; <dependencies>&nbsp; &nbsp; <dependency>&nbsp; &nbsp; &nbsp; <groupId>org.springframework.boot</groupId>&nbsp; &nbsp; &nbsp; <artifactId>spring-boot-starter</artifactId>&nbsp; &nbsp; </dependency>&nbsp; &nbsp; <dependency>&nbsp; &nbsp; &nbsp; <groupId>org.apache.camel.springboot</groupId>&nbsp; &nbsp; &nbsp; <artifactId>camel-spring-boot-starter</artifactId>&nbsp; &nbsp; </dependency>&nbsp; &nbsp; <dependency>&nbsp; &nbsp; &nbsp; <groupId>org.apache.camel.springboot</groupId>&nbsp; &nbsp; &nbsp; <artifactId>camel-jms-starter</artifactId>&nbsp; &nbsp; </dependency>&nbsp; &nbsp; <dependency>&nbsp; &nbsp; &nbsp; <groupId>org.apache.camel.springboot</groupId>&nbsp; &nbsp; &nbsp; <artifactId>camel-exec-starter</artifactId>&nbsp; &nbsp; </dependency>&nbsp; &nbsp; <dependency>&nbsp; &nbsp; &nbsp; <groupId>org.apache.activemq</groupId>&nbsp; &nbsp; &nbsp; <artifactId>activemq-client</artifactId>&nbsp; &nbsp; &nbsp; <version>6.1.6</version>&nbsp; &nbsp; </dependency>&nbsp; </dependencies>&nbsp; <build>&nbsp; &nbsp; <plugins>&nbsp; &nbsp; &nbsp; <plugin>&nbsp; &nbsp; &nbsp; &nbsp; <groupId>org.springframework.boot</groupId>&nbsp; &nbsp; &nbsp; &nbsp; <artifactId>spring-boot-maven-plugin</artifactId>&nbsp; &nbsp; &nbsp; </plugin>&nbsp; &nbsp; </plugins>&nbsp; </build></project>EOF# 阶段2.4:生成 Java 源码echo&nbsp;"[*] 正在生成 camel-app/src/main/java/org/vuln/Application.java..."mkdir&nbsp;-p camel-app/src/main/java/org/vulncat&nbsp;> camel-app/src/main/java/org/vuln/Application.java <<'EOF'package org.vuln;import org.apache.activemq.ActiveMQConnectionFactory;import org.apache.camel.builder.RouteBuilder;import org.apache.camel.component.jms.JmsComponent;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.core.env.Environment;@SpringBootApplicationpublic class Application {&nbsp; &nbsp; public static void main(String[] args) {&nbsp; &nbsp; &nbsp; &nbsp; SpringApplication.run(Application.class, args);&nbsp; &nbsp; }&nbsp; &nbsp; @Bean&nbsp; &nbsp; public JmsComponent jms(Environment&nbsp;env) {&nbsp; &nbsp; &nbsp; &nbsp; String brokerUrl = env.getProperty("ACTIVEMQ_BROKER_URL",&nbsp;"tcp://localhost:61616");&nbsp; &nbsp; &nbsp; &nbsp; ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerUrl);&nbsp; &nbsp; &nbsp; &nbsp; JmsComponent jms = new JmsComponent();&nbsp; &nbsp; &nbsp; &nbsp; jms.setConnectionFactory(cf);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;jms;&nbsp; &nbsp; }&nbsp; &nbsp; @Bean&nbsp; &nbsp; public RouteBuilder&nbsp;vulnerableRoutes() {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;new&nbsp;RouteBuilder() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Override&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; public void&nbsp;configure() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 场景 A:JMS ->&nbsp;exec&nbsp;(使用 vuln.exec 队列)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; from("jms:queue:vuln.exec")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //.to("exec:bash");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .to("exec:whoami")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .log(">>> whoami output:&nbsp;${body}");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 场景 B:JMS -> file 任意文件写入 (队列 vuln.file)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; from("jms:queue:vuln.file")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .to("file:/tmp/outbox?autoCreate=true");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 场景 C:JMS ->&nbsp;exec&nbsp;(使用 incoming 队列,作为另一种演示)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; from("jms:queue:incoming")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .to("exec:bash");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; }}EOFecho&nbsp;"[*] 阶段3/4:启动 Docker 容器环境..."echo&nbsp;"[+] 正在拉取镜像并构建应用..."# 停止并清理旧容器docker compose down -v 2>/dev/null ||&nbsp;true# 启动服务docker compose up -decho&nbsp;""echo&nbsp;"=============================================="echo&nbsp;" CVE-2026-40453 漏洞环境部署完成!"echo&nbsp;" &nbsp;- ActiveMQ 控制台: http://localhost:8161"echo&nbsp;" &nbsp; &nbsp;用户名: admin"echo&nbsp;" &nbsp; &nbsp;密码: admin"echo&nbsp;" &nbsp;- Camel 应用容器已后台运行"echo&nbsp;" &nbsp;- 漏洞触发点: 向 ActiveMQ 的 'incoming' 队列发送消息将触发 exec:bash"echo&nbsp;""echo&nbsp;" &nbsp;- 查看日志: docker logs camel-app"echo&nbsp;" &nbsp;- 进入容器: docker exec -it camel-app /bin/bash"echo&nbsp;"=============================================="

#

#


0x2 漏洞复现

多说一句话,后续尽可能的将复现Pacp共享出来,便于大家做拦截验证

2.1-手动验证

  • 一共是三个,但是本质相同

2.1.1 场景 A:JMS ->camel-exec 命令执行

这一类场景最适合做“最短利用链”验证。先搭一个最容易暴露问题的跨边界链路: 外部生产者只能往消息队列里塞消息头,而路由内部再把消息交给 exec:

建议环境

1. 中间件:&nbsp;&nbsp;ActiveMQ Classic 或任意支持 JMS 属性的 Broker。2. 路由形态:&nbsp; • 消费端:&nbsp;from("jms:queue:vuln.exec")&nbsp; •危险下游:&nbsp;to("exec:whoami") 或 Windows 下&nbsp;to("exec:cmd.exe")3. 依赖模块:&nbsp; •camel-jms&nbsp; •camel-exec&nbsp; •对应 JMS 驱动

最小化示例路由

from("jms:queue:vuln.exec").to("exec:whoami");

如果希望从 Web 侧,可以再加一个 HTTP 入口,把请求头原样转发进 JMS:

345678rest("/demo").post("/exec").to("direct:bridge");
from("direct:bridge").to("jms:queue:vuln.exec");。

Windows 下可替换为:

cAmelExecCommandExecutable=cmd.execAmelExecCommandArgs=/c whoami

2.1.2 场景 B:JMS -> camel-file 任意文件写入

很多系统不会直接接 exec:,却很常见地把 MQ 中的消息落到文件系统。此时攻击者未必需要拿 RCE,只要能劫持输出文件名或路径,就已经越过了原本的安全边界。

建议环境

1.&nbsp;路由形态:&nbsp; •消费端:&nbsp;from("jms:queue:vuln.file")&nbsp; •危险下游: to("file:./outbox?autoCreate=true")
2.依赖模块:&nbsp; •camel-jms&nbsp; •camel-file

最小化示例路由

from("jms:queue:vuln.file").to("file:./outbox?autoCreate=true");

2.2-复现流量特征 (PCAP)

本次复现PCAP:

https://github.com/Kai-One001/PCAP-For-Cybersecurity.rule/blob/main/2026/CVE-2026-40453-Apache-Camel.pcap

#

  • 命令接口

  • 文件写入

  • 命令接口

#


0x3 漏洞原理分析

3.0-[架构定位] 先把受影响模块放同一条消息链

只有先把每个模块在链条中的职责摆清楚,后面看代码时才不会迷失在大量组件实现里。

| | | — | | |

| 层级 | 核心文件 | 角色 | 在漏洞链中的职责 | | — | — | — | — | | 入口过滤层 | components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsHeaderFilterStrategy.java | JMS 默认头过滤策略 | 负责阻止外部消息把 Camel 内部头带入路由 | | 入口过滤层 | core/camel-support/src/main/java/org/apache/camel/support/DefaultHeaderFilterStrategy.java | 通用过滤基类 | 真正执行前缀、正则、集合匹配;漏洞缺陷就在这里暴露 | | 链路桥接层 | components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsBinding.java | JMS <-> Camel 头桥接器 | 先调用过滤器,再把未拦截的外部头写入 Camel 消息头 | | 消息存储层 | core/camel-support/src/main/java/org/apache/camel/support/DefaultMessage.java | 默认消息实现 | 使用大小写不敏感头图保存 Header,导致伪装头与规范头合流 | | 消息存储层 | core/camel-util/src/main/java/org/apache/camel/util/CaseInsensitiveMap.java | 大小写不敏感 Map | 为下游“按规范头名读取”提供基础能力 | | 危险头消费层 | components/camel-exec/src/main/java/org/apache/camel/component/exec/impl/DefaultExecBinding.java | 命令参数绑定 | 从消息头读取 CamelExec* 参数并组装执行命令 | | 危险头消费层 | components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecProducer.java | 命令执行器入口 | 将组装后的命令真正送入执行器 | | 危险头消费层 | components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileProducer.java | 文件写入生产者 | 从消息头读取 CamelFile* 参数并决定最终落盘路径 |

3.1-[核心入口]先看 HeaderFilterStrategy

原本根据漏洞信息时,第一反应应该是去翻camel-exec,这次先不,这次是先追问一个更基础的问题: 攻击者明明只能控制“外部消息头”,这些头为什么有机会在路由内部被当成CamelExecCommandExecutable这样的框架级内部头使用?

先找到外部 Header 进入 Camel 体系时的第一道边界。顺着JMS consumer的逻辑往下看,定位到 JmsBinding.extractHeadersFromJms()。这个函数是关键,因为它一边枚举JMS属性,一边决定哪些头该被拦下,哪些头可以进入Camel消息对象。

// components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsBinding.javawhilewhile&nbsp;(names.hasMoreElements()) {&nbsp; &nbsp;&nbsp;String&nbsp;name = names.nextElement().toString();&nbsp; &nbsp;&nbsp;Object&nbsp;value =&nbsp;JmsMessageHelper.getProperty(jmsMessage, name);&nbsp; &nbsp;&nbsp;if&nbsp;(headerFilterStrategy !=&nbsp;null&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; && headerFilterStrategy.applyFilterToExternalHeaders(name, value, exchange)) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue;&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;String&nbsp;key = jmsKeyFormatStrategy.decodeKey(name);&nbsp; &nbsp; map.put(key, value);}
  • 预期安全边界,本来应该是“外部消息头先过过滤器,Camel内部保留头不能进入业务 Exchange”。
  • 可真正实现里,过滤是否命中完全取决于HeaderFilterStrategy,而一旦没有命中,头就会被map.put(key, value)塞进Camel的消息头图。
  • 换句话说,这里就是“最后一道失守前的边界闸门”。

接着往上追过滤器的具体实现,可以看到 JMS 默认使用的是JmsHeaderFilterStrategy:

// components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsHeaderFilterStrategy.javapublic&nbsp;JmsHeaderFilterStrategy(boolean&nbsp;includeAllJMSXProperties)&nbsp;{&nbsp; &nbsp; setOutFilterStartsWith(DefaultHeaderFilterStrategy.CAMEL_FILTER_STARTS_WITH);&nbsp; &nbsp; setInFilterStartsWith(DefaultHeaderFilterStrategy.CAMEL_FILTER_STARTS_WITH);&nbsp; &nbsp;&nbsp;if&nbsp;(!includeAllJMSXProperties) {&nbsp; &nbsp; &nbsp; &nbsp; initialize();&nbsp; &nbsp; }}
  • 这里传入的CAMEL_FILTER_STARTS_WITH实际上就是框架试图保护内部头空间的规则: 凡是以Camel / camel开头的头,都应该被视为内部头,不让外部带进来。

3.2-[逻辑缺陷] 预期要“拦 Camel 头”,实际却只拦了两种大小写

顺着JmsHeaderFilterStrategy往下追,停在DefaultHeaderFilterStrategy.doFiltering()。看这里的原因很简单: 入口策略把规则交给了公共基类,真正决定是否放行

// core/camel-support/src/main/java/org/apache/camel/support/DefaultHeaderFilterStrategy.javaprivate&nbsp;boolean&nbsp;doFiltering(Direction&nbsp;direction,&nbsp;String&nbsp;headerName,&nbsp;Object&nbsp;headerValue,&nbsp;Exchange&nbsp;exchange) {&nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp;&nbsp;if&nbsp;(startsWith !=&nbsp;null) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(tryHeaderMatch(headerName, startsWith)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;filterOnMatch;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(lowerCase) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; lower = headerName.toLowerCase();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(tryHeaderMatch(lower, startsWith)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;filterOnMatch;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// ...}
private&nbsp;boolean&nbsp;tryHeaderMatch(String&nbsp;headerName,&nbsp;String[] startsWith) {&nbsp; &nbsp;&nbsp;for&nbsp;(String&nbsp;s : startsWith) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;boolean&nbsp;match = headerName.startsWith(s);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(match) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;return&nbsp;false;}
  • startsWith这一支并没有做统一规范化后再比较,而是直接把原始headerName拿来和"Camel"、"camel"做前缀匹配。
  • 它真正能拦住的,是完全以Camel开头或者完全以camel开头的两类头。

这就是设计预期与实现现实之间差异:

•预期边界:&nbsp;&nbsp; 任何 Camel 内部头,不管攻击者怎么折腾大小写,只要语义上还是那个内部头,就不该从外部进入。
•实际实现:&nbsp;&nbsp; 过滤器只认识&nbsp;"Camel"&nbsp;和&nbsp;"camel"&nbsp;两种字面前缀,像 cAmelExecCommandExecutable、CAMelFileName 这类混合大小写变体会被当成“普通外部头”放行。

更值得注意的是,同一份实现里,Set型过滤反而考虑了equalsIgnoreCase

// core/camel-support/src/main/java/org/apache/camel/support/DefaultHeaderFilterStrategy.javaprivate&nbsp;boolean&nbsp;evalFilterMatch(String&nbsp;headerName,&nbsp;String&nbsp;lower,&nbsp;Set<String> filter) {&nbsp; &nbsp;&nbsp;if&nbsp;(isCaseInsensitive()) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(String&nbsp;filterString : filter) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(filterString.equalsIgnoreCase(headerName)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp;else&nbsp;if&nbsp;(lowerCase) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;return&nbsp;false;}

3.3-[攻击链路] 为什么绕过入口后,下游还能按规范头名精确命中

如果分析停在上一步,其实还差最后一块拼图: 就算攻击者用 cAmelExecCommandExecutable 绕过了 JMS 过滤器,下游组件读取的却是标准名字 CamelExecCommandExecutable。这两个字符串并不完全相同,为什么还能连上?

答案藏在 Camel 默认消息实现里。之所以回头去看DefaultMessage,是因为所有组件最终读的都是exchange.getIn().getHeader(...)。只要这里对头名查找是大小写不敏感的,前面的绕过和后面的命中就会自然拼接起来。

// core/camel-support/src/main/java/org/apache/camel/support/DefaultMessage.java/**&nbsp;* This implementation uses a {@link&nbsp;org.apache.camel.util.CaseInsensitiveMap} storing the headers.&nbsp;* This allows us to be able to lookup headers using case insensitive keys.&nbsp;*/&nbsp;&nbsp;public&nbsp;Object&nbsp;getHeader(String&nbsp;name) {&nbsp; &nbsp;&nbsp;if&nbsp;(headers ==&nbsp;null) {&nbsp; &nbsp; &nbsp; &nbsp; headers =&nbsp;createHeaders();&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;if&nbsp;(!headers.isEmpty()) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;headers.get(name);&nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;null;&nbsp; &nbsp; }}

再往底层看,CaseInsensitiveMap直接使用了String.CASE_INSENSITIVE_ORDER:

// core/camel-util/src/main/java/org/apache/camel/util/CaseInsensitiveMap.javapublic&nbsp;class&nbsp;CaseInsensitiveMap&nbsp;extends&nbsp;TreeMap<String,&nbsp;Object> {&nbsp; &nbsp;&nbsp;public&nbsp;CaseInsensitiveMap() {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;super(String.CASE_INSENSITIVE_ORDER);&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;public&nbsp;CaseInsensitiveMap(Map<?&nbsp;extends&nbsp;String, ?> map) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;super(String.CASE_INSENSITIVE_ORDER);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;putAll(map);&nbsp; &nbsp; }}
  • 入口过滤是在大小写敏感地看头名,消息对象内部却在大小写不敏感地存取头名。
  • 攻击者只需要在入口边界把头名改成一个大小写变体,让它躲过过滤;
  • 一旦进入 CaseInsensitiveMap,这个变体就会与规范头名合流。
  • 下游组件完全不需要知道攻击者用了什么花样大小写,它按正常代码读取标准头名,就能把恶意值取出来。

3.4-[爆发点一] 为什么确信这条链能打到 camel-exec

确认了“能进来”和“能命中”之后,下一步就是找真正的危险操作函数。对camel-exec 来说,这个函数非常明确: DefaultExecBinding.readInput() 负责从消息头取出命令相关参数,随后 ExecProducer.process() 直接执行。

public&nbsp;ExecCommand&nbsp;readInput(Exchange&nbsp;exchange,&nbsp;ExecEndpoint&nbsp;endpoint) {&nbsp; &nbsp;&nbsp;Object&nbsp;args = exchange.getIn().removeHeader(EXEC_COMMAND_ARGS);&nbsp; &nbsp;&nbsp;String&nbsp;cmd =&nbsp;getAndRemoveHeader(exchange.getIn(),&nbsp;EXEC_COMMAND_EXECUTABLE, endpoint.getExecutable(),&nbsp;String.class);&nbsp; &nbsp;&nbsp;String&nbsp;dir =&nbsp;getAndRemoveHeader(exchange.getIn(),&nbsp;EXEC_COMMAND_WORKING_DIR, endpoint.getWorkingDir(),&nbsp;String.class);&nbsp; &nbsp; long timeout =&nbsp;getAndRemoveHeader(exchange.getIn(),&nbsp;EXEC_COMMAND_TIMEOUT, endpoint.getTimeout(),&nbsp;Long.class);&nbsp; &nbsp;&nbsp;String&nbsp;exitValuesString&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&nbsp;getAndRemoveHeader(exchange.getIn(),&nbsp;EXEC_COMMAND_EXIT_VALUES, endpoint.getExitValues(),&nbsp;String.class);&nbsp; &nbsp;&nbsp;String&nbsp;outFilePath =&nbsp;getAndRemoveHeader(exchange.getIn(),&nbsp;EXEC_COMMAND_OUT_FILE, endpoint.getOutFile(),&nbsp;String.class);&nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp;&nbsp;return&nbsp;new&nbsp;ExecCommand(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cmd, argsList, dir, timeout, exitValues, input, outFile, useStderrOnEmptyStdout, commandLogLevel);}
public&nbsp;void&nbsp;process(Exchange exchange)&nbsp;throws&nbsp;Exception {&nbsp; &nbsp;&nbsp;ExecCommand&nbsp;execCommand&nbsp;=&nbsp;getBinding().readInput(exchange, endpoint);&nbsp; &nbsp;&nbsp;ExecCommandExecutor&nbsp;executor&nbsp;=&nbsp;endpoint.getCommandExecutor();&nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp;&nbsp;ExecResult&nbsp;result&nbsp;=&nbsp;executor.execute(execCommand);}
  • 如果只看框架设计CamelExecCommandExecutable、CamelExecCommandArgs 这类头本来应该是路由内部受控参数,用于在可信处理器之间传递执行上下文,而不应该由外部消息生产者直接注入。
  • 但是现在,攻击者只要先利用前面的大小写缺陷把这些头带进Exchange,再借助CaseInsensitiveMap让它们被标准名命中ExecProducer就会心安理得地执行攻击者指定的命令。
  • 在这条链上,最后一道失守的防线其实就是 ExecProducer.process() 之前的 DefaultExecBinding.readInput()。因为到了这里,框架已经不再区分“这个头是可信内部逻辑写入的,还是外部消息伪装带进来的”。一旦读到了值,它就会把它们当作合法执行参数组装成 ExecCommand

3.5-[爆发点二] 同样的逻辑为什么还能劫持 camel-file

接着继续找camel-file,是因为它代表另一类非常常见的风险: 不一定要执行命令,只要能影响文件路径,就能形成越权写入、投放后门文件、污染后续处理结果等更隐蔽的攻击面。

首先看框架对这些头的定义:

// components/camel-file/src/main/java/org/apache/camel/component/file/FileConstants.java
@Metadata(description = "(producer) Specifies the name of the file to write ...")public&nbsp;static&nbsp;final&nbsp;String&nbsp;FILE_NAME&nbsp;=&nbsp;Exchange.FILE_NAME;xxxxxx
@Metadata(label = "producer", description = "Is used for overruling `CamelFileName` header ...")public&nbsp;static&nbsp;final&nbsp;String&nbsp;OVERRULE_FILE_NAME&nbsp;=&nbsp;Exchange.OVERRULE_FILE_NAME;

然后看真正消费这些头的位置:

// components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileProducer.javaprotected&nbsp;void&nbsp;doProcess(Exchange&nbsp;exchange) throws&nbsp;Exception&nbsp;{&nbsp; &nbsp; final&nbsp;String&nbsp;existing = exchange.getIn().getHeader(FileConstants.FILE_NAME,&nbsp;String.class);&nbsp; &nbsp;&nbsp;String&nbsp;target =&nbsp;createFileName(exchange);&nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp; exchange.getIn().removeHeader(Exchange.OVERRULE_FILE_NAME);&nbsp; &nbsp; exchange.getIn().setHeader(FileConstants.FILE_NAME, existing);}// 2public&nbsp;String&nbsp;createFileName(Exchange&nbsp;exchange) {&nbsp; &nbsp;&nbsp;Object&nbsp;overrule = exchange.getIn().getHeader(FileConstants.OVERRULE_FILE_NAME);&nbsp; &nbsp; final&nbsp;Object&nbsp;value =&nbsp;getOverrule(exchange, overrule);&nbsp; &nbsp;&nbsp;// ...}private&nbsp;static&nbsp;Object&nbsp;getOverrule(Exchange&nbsp;exchange,&nbsp;Object&nbsp;overrule) {&nbsp; &nbsp;&nbsp;if&nbsp;(overrule !=&nbsp;null) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; value = exchange.getIn().getHeader(FileConstants.FILE_NAME);&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;return&nbsp;value;}// 3public&nbsp;void&nbsp;writeFile(Exchange&nbsp;exchange,&nbsp;String&nbsp;fileName) throws&nbsp;GenericFileOperationFailedException&nbsp;{&nbsp; &nbsp;&nbsp;if&nbsp;(endpoint.isAutoCreate()) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;String&nbsp;name =&nbsp;FileUtil.normalizePath(fileName);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;File&nbsp;file =&nbsp;new&nbsp;File(name);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;String&nbsp;directory = file.getParent();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(directory !=&nbsp;null) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; operations.buildDirectory(directory, absolute);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;boolean&nbsp;success = operations.storeFile(fileName, exchange, -1);}

这里的问题与camel-exec 是同构的。

  • CamelFileName / CamelOverruleFileName应该是路由内部用来控制输出文件名的保留头;
  • 实际中呢,只要攻击者能通过大小写变体把它们偷运进Exchange,下游GenericFileProducer 就会把它们作为正常的输出路径参数使用。
  • 尤其当端点开启autoCreate=true时,攻击者不仅能控制文件名,往往还可以顺手控制目录层级。

如果目标业务把 MQ 消息落盘到某个被其他系统消费的目录,那么这就不只是“任意写一个文件”那么简单了。它可能进一步变成:

1.&nbsp;向任务目录投放伪造作业文件,诱导后续处理链执行;2.&nbsp;覆盖关键业务输出,制造数据污染;3.&nbsp;在某些部署结构下,向 Web 可访问目录写入可执行脚本或恶意内容。

#

3.6-[攻击链路] 从注入点到爆发点的完整闭环

现在整条链已经可以完整闭环了。我们重新串起来,会发现这个漏洞并不是单点编码失误,而是三个“各自看起来合理”的设计在边界上发生了危险叠加:

1.&nbsp;JMS/SJMS 希望用 HeaderFilterStrategy 阻止外部消息伪装内部头。2.&nbsp;Camel 核心为了易用性,默认把消息头放进 CaseInsensitiveMap。3.&nbsp;camel-exec、camel-file 等下游组件按规范内部头名读取参数。

完整调用链如下:

// 外部 HTTP/JMS 生产者&nbsp; ->&nbsp;注入大小写变体头(如 cAmelExecCommandExecutable / cAmelFileName)&nbsp; ->&nbsp;JMS Consumer / SJMS Consumer&nbsp; ->&nbsp;JmsBinding.extractHeadersFromJms()&nbsp; ->&nbsp;HeaderFilterStrategy.applyFilterToExternalHeaders()&nbsp; ->&nbsp;DefaultHeaderFilterStrategy.doFiltering() 前缀匹配遗漏混合大小写&nbsp; ->&nbsp;map.put(key, value) 写入 Camel Message&nbsp; ->&nbsp;DefaultMessage / CaseInsensitiveMap 以不区分大小写方式存储与检索&nbsp; ->&nbsp;下游组件按规范头名 getHeader()/removeHeader()&nbsp; ->&nbsp;爆发点 A: DefaultExecBinding.readInput() -> ExecProducer.process() -> 命令执行&nbsp; ->&nbsp;爆发点 B: GenericFileProducer.createFileName()/writeFile() -> 文件写入

#


#

0x4 修复建议

1、升级最新版本:将组件升级安全版本

https://camel.apache.org/security/CVE-2026-40453.html

2、临时防护措施:

  • 限制访问:严格限制谁可以向JMS/消息队列生产消息;对桥接MQ的HTTP入口增加鉴权,不允许匿名或低信任租户直接投递

  • 防火墙 / WAF:可拦截包含异常Camel内部头特征的请求头,例如 :(?i)^camel(exec|file);

  • 危险组件隔离:避免把jms:或其他外部可写消息源直接连接到exec:、file:、bean:等受Header强驱动的端点,中间增加显式映射层,只复制白名单字段。

  • 审计与检测:重点检索日志、Broker 属性和流量中是否出现非常规大小写的Camel 内部头,如cAmelExecCommandExecutable、CAMelFileName、cAmelOverruleFileName。这类特征在正常业务里几乎没有正当理由出现。


免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。

/** 怎么样怎么样?专门找今天发,因为假期最后一天啦,同志们要注意时间管理呀**/


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:404号浪漫 404号浪漫 404号浪漫《Apache Camel 远程代码执行漏洞 | CVE-2026-40453复现&研究》

在地下_马识途_摘录(1) 网络安全文章

在地下_马识途_摘录(1)

文章总结: 本文摘录自马识途《在地下》,详述中共地下党员黎强(本名李长亨)长期潜伏国民党中统机关的经历。他利用省特委会情报处秘书身份,在1947年六一大逮捕前送
评论:0   参与:  0