文章总结: 本文解析CVE-2015-5254,ApacheActiveMQ5.13.0前版本因JMSObjectMessage反序列化缺乏验证导致RCE。攻击者通过OpenWire协议发送恶意序列化对象,利用ROMEgadget链在消费者端执行任意命令。文章详细阐述了JMS通信机制及漏洞触发原理,并提供jmet工具利用示例,建议升级组件并加强对反序列化数据的输入校验。 综合评分: 85 文章分类: 漏洞分析,漏洞POC,渗透测试
CVE-2015-5254:Apache ActiveMQ JMS ObjectMessage反序列化漏洞
原创
晨星安全团队
晨星安全团队
2025年12月27日 13:23 湖南
JMS ObjectMessage反序列化漏洞
(CVE-2015-5254)
漏洞基本信息
Apache ActiveMQ 5.x版本(在5.13.0之前)在处理JMS ObjectMessage时,没有对反序列化对象进行足够的输入验证和类限制。
攻击者可以通过OpenWire传输协议(默认端口61616)发送一个序列化负载,当管理员反序列化该负载时,会执行负载中嵌入的任意代码。
具体而言,JMS ObjectMessage依赖Java的本机序列化机制(通过Serializable接口和ObjectInputStream.readObject()方法)。如果未对输入进行验证,反序列化过程可能会调用classpath中的现有可序列化类(称为”gadget”),从而实现文件写入、动态方法调用或命令执行
下载源码
https://activemq.apache.org/components/classic/download/
JMS核心内容
JMS(Java Message Service)是Java平台的消息服务规范,由Sun(后来Oracle)定义,用于实现不同系统或组件间的异步通信与解耦。
- 模式
- 点对点(Point-to-Point,Queue):消息发送到队列,一个消息只能被一个消费者消费
- 发布/订阅(Publish/Subscribe,Topic):消息发送到主题,多个订阅者都能收到
- 消息类型:
TextMessage(文本)
BytesMessage(字节流)
ObjectMessage(序列化对象)
MapMessage(键值对)
StreamMessage(数据流)
简单理解:**JMS就是Java世界里的”消息中间件标准API”**,提供统一接口,底层可以由ActiveMQ、HornetQ、RabbitMQ JMS 适配器等实现
查看源码:
ObjectMessage属于javax.jms包下,用到了java.io.Serializable
package javax.jms;
// 任何能被放入 ObjectMessage 的对象必须实现 Serializable,这样对象可以在网络中传输或持久化
import java.io.Serializable;
// ObjectMessage 是 JMS 中的一种消息类型
// 它继承了 Message 接口,说明它本身也是一种消息,只是消息体部分存放的是Serializable对象
public interface ObjectMessage extends Message {
// 用于设置消息体中的对象,参数必须是 `Serializable`,否则无法传输
// 如果 JMS Provider 在设置时出现问题,会抛出 `JMSException`
void setObject(Serializable var1) throws JMSException;
// 用于获取消息体中的对象
Serializable getObject() throws JMSException;
}
JMS关键接口说明
注意这里有一个消息生产者:MessageProducer
package javax.jms;
public interface MessageProducer {
/**
* 设置是否禁用消息ID
* @param z true 表示发送的消息不生成消息ID,false
*/
void setDisableMessageID(boolean z) throws JMSException;
/**
* 获取是否禁用消息ID
* @returntrue 表示消息ID被禁用,false 表示消息ID未禁用
*/
boolean getDisableMessageID() throws JMSException;
/**
* 设置是否禁用消息时间戳
* @param z true 表示发送的消息不带时间戳,false 表示带时间戳
*/
void setDisableMessageTimestamp(boolean z) throws JMSException;
/**
* 获取是否禁用消息时间戳
* @returntrue 表示时间戳被禁用,false 表示时间戳未禁用
*/
boolean getDisableMessageTimestamp() throws JMSException;
/**
* 设置消息的传送模式
* @param i 传送模式,可以是 DeliveryMode.PERSISTENT 或
DeliveryMode.NON_PERSISTENT
*/
void setDeliveryMode(int i) throws JMSException;
/**
* 获取当前消息的传送模式
* @return 当前的传送模式
*/
int getDeliveryMode() throws JMSException;
/**
* 设置消息的优先级
* @param i 优先级值,范围 0~9,数字越大优先级越高
*/
void setPriority(int i) throws JMSException;
/**
* 获取消息的优先级
* @return 当前优先级值
*/
int getPriority() throws JMSException;
/**
* 设置消息的有效时间(TTL, Time To Live)
* @param j 消息存活时间(毫秒),0 表示永不过期
*/
void setTimeToLive(long j) throws JMSException;
/**
* 获取消息的有效时间(TTL)
* @return 消息存活时间(毫秒)
*/
long getTimeToLive() throws JMSException;
/**
* 获取消息发送的目标目的地(Destination)
* @return 消息目的地
*/
Destination getDestination() throws JMSException;
/**
* 关闭消息生产者,释放资源
*/
void close() throws JMSException;
/**
* 发送消息到默认目的地
* @param message 要发送的消息对象
*/
void send(Message message) throws JMSException;
/**
* 发送消息到默认目的地,同时指定传送模式、优先级和有效时间
* @param message 要发送的消息对象
* @param i 传送模式
* @param i2 优先级
* @param j 消息有效时间
*/
void send(Message message, int i, int i2, long j) throws JMSException;
/**
* 发送消息到指定目的地
* @param destination 目标目的地
* @param message 要发送的消息对象
*/
void send(Destination destination, Message message) throws JMSException;
/**
* 发送消息到指定目的地,同时指定传送模式、优先级和有效时间
* @param destination 目标目的地
* @param message 要发送的消息对象
* @param i 传送模式
* @param i2 优先级
* @param j 消息有效时间
*/
void send(Destination destination, Message message, int i, int i2, long j)
throws JMSException;
}
当然还有消息的消费者
package javax.jms;
public interface MessageConsumer {
/**
* 获取消息选择器(Message Selector)
* 消息选择器用于基于消息属性对消息进行过滤
* @return 当前的消息选择器表达式,如果没有则返回 null
*/
String getMessageSelector() throws JMSException;
/**
* 获取当前设置的消息监听器
* 消息监听器用于异步接收消息
* @return 当前的 MessageListener 对象,如果没有则返回 null
*/
MessageListener getMessageListener() throws JMSException;
/**
* 设置消息监听器,用于异步接收消息
* 当消息到达时,JMS 提供者会调用 listener 的 onMessage 方法处理消息
* @param messageListener 要设置的消息监听器对象
*/
void setMessageListener(MessageListener messageListener) throws JMSException;
/**
* 同步接收消息,方法会阻塞直到有消息可接收
* @return 接收到的消息对象,如果消费者关闭或发生异常可能返回 null
*/
Message receive() throws JMSException;
/**
* @return 接收到的消息对象,如果超时返回 null
*/
Message receive(long j) throws JMSException;
/**
* 非阻塞方式接收消息
* 如果当前没有消息可接收,则立即返回 null
* @return 接收到的消息对象,如果没有消息返回 null
*/
Message receiveNoWait() throws JMSException;
/**
* 关闭消息消费者,释放资源
* 一旦关闭,消费者将无法再接收消息
*/
void close() throws JMSException;
}
JMS消息收发流程代码示例
核心代码如下:
protected Message doExecuteRequest(Session session, Queue queue, Message requestMessage) throws JMSException {
TemporaryQueue responseQueue = null;
MessageProducer producer = null;
MessageConsumer consumer = null;
Message var9;
try {
if (jms11Available) {
responseQueue = session.createTemporaryQueue();
producer = session.createProducer(queue);
consumer = session.createConsumer(responseQueue);
requestMessage.setJMSReplyTo(responseQueue);
((MessageProducer)producer).send(requestMessage);
} else {
QueueSession queueSession = (QueueSession)session;
responseQueue = queueSession.createTemporaryQueue();
QueueSender sender = queueSession.createSender(queue);
producer = sender;
consumer = queueSession.createReceiver(responseQueue);
requestMessage.setJMSReplyTo(responseQueue);
sender.send(requestMessage);
}
long timeout = this.getReceiveTimeout();
var9 = timeout > 0L ? ((MessageConsumer)consumer).receive(timeout) :
((MessageConsumer)consumer).receive();
} finally {
JmsUtils.closeMessageConsumer((MessageConsumer)consumer);
JmsUtils.closeMessageProducer((MessageProducer)producer);
if (responseQueue != null) {
responseQueue.delete();
}
}
return var9;
}
发送请求消息部分代码
try {
if (jms11Available) {
// JMS 1.1+ 路径
responseQueue = session.createTemporaryQueue();
producer = session.createProducer(queue); // 创建生产者
consumer = session.createConsumer(responseQueue);
requestMessage.setJMSReplyTo(responseQueue); // 设置回复地址
((MessageProducer)producer).send(requestMessage); // 发送消息
} else {
// JMS 1.0.2b 路径
QueueSession queueSession = (QueueSession)session;
responseQueue = queueSession.createTemporaryQueue();
QueueSender sender = queueSession.createSender(queue); // 创建发送者
producer = sender; // 赋值给通用变量
consumer = queueSession.createReceiver(responseQueue);
requestMessage.setJMSReplyTo(responseQueue); // 设置回复地址
sender.send(requestMessage); // 发送消息
}
((MessageProducer)producer).send(requestMessage);
- 作用:这是最终执行消息发送操作的方法调用。它将消息正式放入 JMS 系统中。
- 执行过程:
- 客户端库(如 ActiveMQ-client)将
requestMessage对象(包括其头属性JMSReplyTo和消息体)序列化为一种可以通过网络传输的格式(如OpenWire协议格式)。 - 通过已建立的TCP连接(隐藏在
session和connection背后)将序列化后的数据包发送给ActiveMQ代理(Broker)。 - ActiveMQ代理接收数据包,将其解析后,存储到指定的目标
queue中。
- 完成后:此时,消息已经成功发送并存在于服务器的队列里,等待任何消费者来取走它。
producer:攻击者利用同样的机制(创建一个生产者并调用send())来向队列投递恶意的请求消息
responseQueue+consumer:这是客户端受害的关键环节。如果攻击者能够将恶意消息直接发送到这个临时
队列,正在receive()的consumer就会接收到它,并在解析消息时触发反序列化漏洞
消费者解析的部分被隐藏在了 ActiveMQ 客户端库的底层实现里,具体来说,是在consumer.receive()方法的内部
获取超时时间
long timeout = this.getReceiveTimeout();
情况一:设置了超时时间(timeout > 0)
((MessageConsumer)consumer).receive(timeout)
- 行为:这是一个阻塞调用。调用此方法的线程会挂起,直到发生以下三种情况之一:
- 成功收到消息:一条消息到达了临时响应队列
responseQueue。该方法会立即返回这条消息 (Message对象),代码继续执行 - 超时:在指定的
timeout毫秒数内,没有收到任何消息。该方法会返回null,然后代码继续执行 - 连接中断:底层的JMS连接被关闭或发生错误。该方法会抛出
JMSException
情况二:未设置超时时间或超时时间为0(timeout <= 0)
((MessageConsumer)consumer).receive()
- 行为:这也是一个阻塞调用,但它是无限期等待。调用此方法的线程会一直挂起,直到:
- 成功收到消息:收到消息并返回
- 连接中断:连接失败,抛出
JMSException它不会因为超时而返回null
在finally块中,代码确保了消费者、生产者和临时队列都会被正确关闭和删除,以避免资源泄漏
finally {
// 使用 JmsUtils 工具类的 closeMessageConsumer() 方法
// 将 consumer 对象强制转换为 MessageConsumer 类型
// 确保消费者资源被正确关闭,避免资源泄漏
JmsUtils.closeMessageConsumer((MessageConsumer)consumer);
JmsUtils.closeMessageProducer((MessageProducer)producer);
if (responseQueue != null) {
// 如果不为 null,则调用 delete() 方法删除队列
responseQueue.delete();
}
}
漏洞原理与源码深度解析
利用工具与命令
下载利用工具
https://github.com/matthiaskaiser/jmet
java -jar jmet-0.1.0-all.jar -Q event -I ActiveMQ -s -Y "touch /tmp/success" -YpROME your-ip 61616
参数说明
java -jar jmet-0.1.0-all.jar
- 用Java运行JAR包
jmet-0.1.0-all.jar,这是JMET的执行文件 -jar表示直接运行JAR包
-Q event
- 指定目标队列(Queue)名称
- 这里
event是ActiveMQ中存在的队列名 - JMET会把payload注入到这个队列
-I ActiveMQ
- 指定目标类型为ActiveMQ
- JMET 支持多种消息队列,-I 告诉工具你要攻击哪种
-s
- 开启发送模式(Send mode)
- 表示JMET会向目标队列发送payload,而不是监听或测试
-Y "touch /tmp/success"
- 指定payload 命令,就是你希望执行的命令
- 这里是 Linux 命令:在目标系统/tmp 下创建一个文件success
- JMET 会将这个命令通过反序列化漏洞发送到 ActiveMQ 执行
-Yp ROME
- 指定payload 类型(Payload gadget chain),这里用的是ROME
- ActiveMQ 有多种可利用 gadget chain(CommonsCollections、ROME、BeanShell 等),选择不同payload chain 会影响反序列化利用方式
your-ip 61616
- 目标主机 IP 和端口。
- your-ip → ActiveMQ服务的地址(必须是完整IP,如192.168.194.128)
- 61616 → ActiveMQ默认TCP端口
-END-
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:晨星安全团队 晨星安全团队《CVE-2015-5254:Apache ActiveMQ JMS ObjectMessage反序列化漏洞》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论