Apache Dubbo CVE-2023-23638 JavaNative 反序列化漏洞分析
作者:jweny safe6 Koourl 0x01 写在前面 Apache Dubbo 是一款开源的一个高性能的 Java RPC 框架,致力于提供高性能透明化的 RPC 远程服务调用方案,常用于开发微服务和分布式架构相关的需求。 Apache Dubbo 默认支持泛化引用由服务端 API 接口暴露的所有方法,这些调用统一由 GenericFilter 处理。GenericFilter 将根据
0x01 写在前面
Apache Dubbo 是一款开源的一个高性能的 Java RPC 框架,致力于提供高性能透明化的 RPC 远程服务调用方案,常用于开发微服务和分布式架构相关的需求。
Apache Dubbo 默认支持泛化引用由服务端 API 接口暴露的所有方法,这些调用统一由 GenericFilter 处理。GenericFilter 将根据客户端提供的接口名、方法名、方法参数类型列表,根据反射机制获取对应的方法,再根据客户端提供的反序列化方式将参数进行反序列化成 pojo 对象。本漏洞通过 JavaNative 来实现反序列化,进而触发特定 Gadget ,最终导致了远程代码执行。
| 存在漏洞版本 | 安全版本 |
|---|---|
| 2.7.x <= 2.7.21 | 2.7.x >= 2.7.22 |
| 3.0.x <= 3.0.13 | 3.0.x >= 3.0.14 |
| 3.1.x <= 3.1.5 | 3.1.x >= 3.1.6 |
0x02 漏洞环境搭建
漏洞环境可使用 lz2y 师傅文章 CVE-2021-3017
Step1: 启动 zookeeeper

Step2: 启动 dubbo provider。注册接口 demoService,方法为 sayHello。




0x03 漏洞分析
请求处理逻辑
Dubbo 处理请求的完整处理过程如下:
validateClass:110, SerializeClassChecker (org.apache.dubbo.common.utils)
realize0:398, PojoUtils (org.apache.dubbo.common.utils)
realize:220, PojoUtils (org.apache.dubbo.common.utils)
realize:107, PojoUtils (org.apache.dubbo.common.utils)
invoke:96, GenericFilter (org.apache.dubbo.rpc.filter)
invoke:61, FilterNode (org.apache.dubbo.rpc.protocol)
invoke:38, ClassLoaderFilter (org.apache.dubbo.rpc.filter)
invoke:61, FilterNode (org.apache.dubbo.rpc.protocol)
invoke:41, EchoFilter (org.apache.dubbo.rpc.filter)
invoke:61, FilterNode (org.apache.dubbo.rpc.protocol)
reply:145, DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo)
received:152, DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo)
received:177, HeaderExchangeHandler (org.apache.dubbo.remoting.exchange.support.header)
received:51, DecodeHandler (org.apache.dubbo.remoting.transport)
run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:41, InternalRunnable (org.apache.dubbo.common.threadlocal)
run:748, Thread (java.lang)
Dubbo 使用 DecodeHandler#received 方法接受来自 socket 的连接,当收到请求时会先调用 DecodeHandler#decode 处理请求。
decode 完成之后,将调用 HeaderExchangeHandler#received,若为泛型引用,经过FilterNode、ClassLoaderFilter,最终调用 GenericFilter#invoke 方法。
invoke 函数会对传入的 Invocation 对象进行校验:
- 要求方法名等于 $invoke 或 $invoke_async
- 要求参数长度 3
- 要求invoker 的接口不能继承自 GenericService
校验通过后会通过 getArguments() 方法获取参数。第一个参数为方法名,第二个参数为方法名的类型,第三个参数为args。
然后通过 findMethodByMethodSignature 反射寻找服务端提供的方法(也就是章节2漏洞环境中的 sayHello 方法),如果没找到将抛出异常。

然后,通过获取请求中的 generic 参数来选择通过哪种方式将反序列化参数成 pojo 对象。一共有以下几种类型:
- DefaultGenericSerialization(true)
- JavaGenericSerialization(nativejava)
- BeanGenericSerialization(bean)
- ProtobufGenericSerialization(protobuf-json)
- GenericReturnRawResult(raw.return)

通过 notice 可知,NativeJava 方式是默认被禁止的。如果启用了 NativeJava 反序列化,就会调用 deserialize.readObject 触发反序列化。

JavaNative
GenericFilter#invoke 函数实现了不同的 generic 相应的反序列化逻辑。最后当 generic 为 raw.return 类型时,会进入 PojoUtils.realize。
PojoUtils.realize 对参数进行反序列化操作。函数传入的是 Object 类型的对象数组,即可以传入所有类,并且是处理多组需要序列化的数据,传入的 objs 为从 inv 中获取的参数,如果三组长度不相等,即传入的参数不匹配,则抛出异常。然后对传入的对象生成一个 Object 数组,然后对 Object 数组进行遍历,以便对传入的所有项继续进行序列化操作。


进入到三个参数的重载方法,继续进行反序列化,使用 realize0 方法进行反序列化。realize0 方法中实现了通过反射调用 set 方法。

回过头来再看 GenericFilter#invoke ,判断 JavaNative 是否开启是通过判断 Configuration 的 dubbo.security.serialize.generic.native-java-enable 。

漏洞利用
总结一下我们现在有以下条件:
- 构造 generic 为 raw.return ,然后调用类的 set 方法
- 将 Configuration 的
dubbo.security.serialize.generic.native-java-enable属性设置为 True 即可启用 JavaNative - 利用 JavaNative 反序列化执行 payload
所以现在的关键在于找到一个类能够通过 set 方法修改 Configuration ,整条路就通了。
还真的有一个这样的类 org.apache.dubbo.common.utils。
public class ConfigUtils {
...
public static void setProperties(Properties properties) {
PROPERTIES = properties;
}
...
}
所以整体利用流程为:
- 构造 properties 设置
dubbo.security.serialize.generic.native-java-enable为 True - 构造 generic 为
raw.return,反射调用org.apache.dubbo.common.utils的setProperties(properties),启用 JavaNative - 利用 JavaNative 执行反序列化 payload
private static void enableJavaNative(Hessian2ObjectOutput out) throws IOException {
Properties properties = new Properties();
properties.setProperty("dubbo.security.serialize.generic.native-java-enable","TRUE");
HashMap jndi = new HashMap();
jndi.put("class", "org.apache.dubbo.common.utils.ConfigUtils");
jndi.put("properties", properties);
out.writeObject(new Object[]{jndi});
HashMap map = new HashMap();
map.put("generic", "raw.return");
out.writeObject(map);
}
0x04 补丁分析
2.7.21-2.7.12更新链接:
https://github.com/apache/dubbo/commit/4f664f0a3d338673f4b554230345b89c580bccbb
补丁新增了一个properties 配置 CLASS_DESERIALIZE_CHECK_SERIALIZABLE 和 序列化类检查函数 SerializeClassChecker ()
String CLASS_DESERIALIZE_CHECK_SERIALIZABLE = "dubbo.application.check-serializable";
CLASS_DESERIALIZE_CHECK_SERIALIZABLE 默认值为 true

SerializeClassChecker#validateClass 函数讲校验当前类是否可序列化。由于本次利用的 org.apache.dubbo.common.utils 为非序列化类,将会直接返回 error。

在 realize0() 和 JavaBeanSerializeUtil 中均调 validateClass 用当前类进行校验。

测试修复后的版本:


0x05 其他
本文仅分析了通过 generic 为 raw.return 时,通过 org.apache.dubbo.common.utils 修改 Configuration 来启用 JavaNative,但根据补丁来看,generic 为 bean 时也可能利用,有兴趣的师傅可自己探索。
0x06 引用
- https://github.com/lz2y/DubboPOC
- https://github.com/apache/dubbo/commit/4f664f0a3d338673f4b554230345b89c580bccbb
- https://cn.dubbo.apache.org/zh-cn/docsv2.7/user/configuration/properties/
- https://mp.weixin.qq.com/s?__biz=MzA4NzUwMzc3NQ==&mid=2247488856&idx=1&sn=ee37514a5bfbf8c35f4ec661a4c7d45a&chksm=903933a8a74ebabecaf9428995491494f20e5b24a15f8d52e79d3a9dac601620c21d097cdc1f&scene=21#wechat_redirect