CVE-2015-5254:ApacheActiveMQJMSObjectMessage反序列化漏洞

admin 2025-12-29 01:07:16 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文解析CVE-2015-5254,ApacheActiveMQ5.13.0前版本因JMSObjectMessage反序列化缺乏验证导致RCE。攻击者通过OpenWire协议发送恶意序列化对象,利用ROMEgadget链在消费者端执行任意命令。文章详细阐述了JMS通信机制及漏洞触发原理,并提供jmet工具利用示例,建议升级组件并加强对反序列化数据的输入校验。 综合评分: 85 文章分类: 漏洞分析,漏洞POC,渗透测试


cover_image

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)定义,用于实现不同系统或组件间的异步通信与解耦。

  • 模式
  1. 点对点(Point-to-Point,Queue):消息发送到队列,一个消息只能被一个消费者消费
  2. 发布/订阅(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 系统中。
  • 执行过程
  1. 客户端库(如 ActiveMQ-client)将requestMessage对象(包括其头属性JMSReplyTo和消息体)序列化为一种可以通过网络传输的格式(如OpenWire协议格式)。
  2. 通过已建立的TCP连接(隐藏在sessionconnection背后)将序列化后的数据包发送给ActiveMQ代理(Broker)。
  3. ActiveMQ代理接收数据包,将其解析后,存储到指定的目标queue中。
  • 完成后:此时,消息已经成功发送并存在于服务器的队列里,等待任何消费者来取走它。

producer:攻击者利用同样的机制(创建一个生产者并调用send())来向队列投递恶意的请求消息

responseQueue+consumer:这是客户端受害的关键环节。如果攻击者能够将恶意消息直接发送到这个临时

队列,正在receive()consumer就会接收到它,并在解析消息时触发反序列化漏洞

消费者解析的部分被隐藏在了 ActiveMQ 客户端库的底层实现里,具体来说,是在consumer.receive()方法的内部

获取超时时间

long timeout = this.getReceiveTimeout();

情况一:设置了超时时间(timeout > 0)

((MessageConsumer)consumer).receive(timeout)
  • 行为:这是一个阻塞调用。调用此方法的线程会挂起,直到发生以下三种情况之一:
  1. 成功收到消息:一条消息到达了临时响应队列responseQueue。该方法会立即返回这条消息 (Message对象),代码继续执行
  2. 超时:在指定的timeout毫秒数内,没有收到任何消息。该方法会返回null,然后代码继续执行
  3. 连接中断:底层的JMS连接被关闭或发生错误。该方法会抛出JMSException

情况二:未设置超时时间或超时时间为0(timeout <= 0)

((MessageConsumer)consumer).receive()
  • 行为:这也是一个阻塞调用,但它是无限期等待。调用此方法的线程会一直挂起,直到:
  1. 成功收到消息:收到消息并返回
  2. 连接中断:连接失败,抛出JMSException它不会因为超时而返回null

finally块中,代码确保了消费者、生产者和临时队列都会被正确关闭和删除,以避免资源泄漏

finally {
&nbsp; &nbsp; // 使用 JmsUtils 工具类的 closeMessageConsumer() 方法
&nbsp; &nbsp; // 将 consumer 对象强制转换为 MessageConsumer 类型
&nbsp; &nbsp; // 确保消费者资源被正确关闭,避免资源泄漏
&nbsp; &nbsp; JmsUtils.closeMessageConsumer((MessageConsumer)consumer);
&nbsp; &nbsp; JmsUtils.closeMessageProducer((MessageProducer)producer);
&nbsp; &nbsp;&nbsp;if&nbsp;(responseQueue != null) {
&nbsp; &nbsp; &nbsp; &nbsp; // 如果不为 null,则调用 delete() 方法删除队列
&nbsp; &nbsp; &nbsp; &nbsp; responseQueue.delete();
&nbsp; &nbsp; }
}

漏洞原理与源码深度解析

利用工具与命令

下载利用工具

https://github.com/matthiaskaiser/jmet
java -jar jmet-0.1.0-all.jar -Q event -I ActiveMQ -s -Y&nbsp;"touch /tmp/success"&nbsp;-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反序列化漏洞》

评论:0   参与:  0