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 |
漏洞环境可使用 lz2y 师傅文章 CVE-2021-3017
Step1: 启动 zookeeeper
Step2: 启动 dubbo provider。注册接口 demoService,方法为 sayHello。
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 对象进行校验:
校验通过后会通过 getArguments() 方法获取参数。第一个参数为方法名,第二个参数为方法名的类型,第三个参数为args。
然后通过 findMethodByMethodSignature 反射寻找服务端提供的方法(也就是章节2漏洞环境中的 sayHello 方法),如果没找到将抛出异常。
然后,通过获取请求中的 generic 参数来选择通过哪种方式将反序列化参数成 pojo 对象。一共有以下几种类型:
通过 notice 可知,NativeJava 方式是默认被禁止的。如果启用了 NativeJava 反序列化,就会调用 deserialize.readObject
触发反序列化。
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
。
总结一下我们现在有以下条件:
dubbo.security.serialize.generic.native-java-enable
属性设置为 True 即可启用 JavaNative所以现在的关键在于找到一个类能够通过 set 方法修改 Configuration ,整条路就通了。
还真的有一个这样的类 org.apache.dubbo.common.utils
。
public class ConfigUtils {
...
public static void setProperties(Properties properties) {
PROPERTIES = properties;
}
...
}
所以整体利用流程为:
dubbo.security.serialize.generic.native-java-enable
为 Trueraw.return
,反射调用 org.apache.dubbo.common.utils
的 setProperties(properties)
,启用 JavaNativeprivate 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);
}
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 用当前类进行校验。
测试修复后的版本:
本文仅分析了通过 generic 为 raw.return
时,通过 org.apache.dubbo.common.utils
修改 Configuration 来启用 JavaNative,但根据补丁来看,generic 为 bean 时也可能利用,有兴趣的师傅可自己探索。