0x00、前言
Dubbo反序列化漏洞(CVE-2023-23638)复现与分析
0x01、Dubbo描述
Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架。Apache Dubbo提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。
0x02、漏洞描述
Dubbo 泛化调用时由于反序列化检查机制实现存在缺陷,可访问目标服务的攻击者可能在服务提供方上执行恶意代码。利用此漏洞需知道接口全限定名、方法名、入参及返参类型。攻击者成功利用此漏洞可造成远程代码执行。
0x03、漏洞范围
Apache Dubbo 2.7.x <= 2.7.21
Apache Dubbo 3.0.x <= 3.0.13
Apache Dubbo 3.1.x <= 3.1.5
0x04、环境搭建
环境需要zookeeper+dubbo
zookeeper搭建:
zookeeper官方下载:https://zookeeper.apache.org/releases.html
需要修改config,主要是添加dataDir/dataLogDir数据和日志路径
直接运行bin目录下的zkServer.cmd即可
dubbo环境搭建:
可参考dubbo环境搭建
或者使用已经搭建好的均可:
https://github.com/lz2y/DubboPOC/
https://github.com/X1r0z/CVE-2023-23638
本环境使用https://github.com/lz2y/DubboPOC/ 导入的漏洞环境
<groupId>org.apache.dubbo</groupId> |
最后更新下maven即可
0x05、漏洞复现
可选择jdbcRowSetImpl链加载ldap进行复现
触发位置
0x06、原理分析
泛型调用
dubbo提供程序接口公开的任意方法的泛型调用,通过解码流数据后由GenericFilter
类进行处理
其中要求:
inv
对象的方法名等于$invoke
或者$invokeAsync
inv
参数不能为空,参数数量为3个参数并且要求不继承GenericService
类
if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC)) |
获取inv
其中的name
、types
、args
属性对应inv
对象的三个参数(方法名、方法类型、方法参数)
接着调用ReflectUtils.findMethodByMethodSignature
方法判断改对象是否是系统服务提供的方法,不为则抛出异常,这也是实现的必要条件之一
因此payload中会在流中写入服务提供端的调用方法
接下来根据generic
属性进行不同的序列化操作
方式一:SerializeClassChecker绕过黑名单执行setter
其中如果generic
属性为raw.return
,则会调用PojoUtils.realize
方法进行反序列化操作
该方法会循环数组对象调用realize
方法,进而调用realize0
方法
realize0方法中判断生成的pojo对象,其中有判断pojo对象为泛型的情况
pojo
对象为泛型,则会从pojo
对象中获取key
为class
的值作为将实例化出来的类名,并经过SerializeClassChecker.getInstance().validateClass
检查该类名是否在序列化类检查器的黑名单中
默认情况序列化检查OPEN_CHECK_CLASS
是开启的,因此会对类名进行黑名单检查,会循环遍历类名前缀是否匹配黑名单进行抛出异常
因此关闭序列化检查也是实现的必要条件之一,解决方法可重新传递一个Instance
变量值去替换重新生成new SerializeClassChecker()
序列化检查器,让Instance
不为空,即在调用getInstance
方法时则不会new SerializeClassChecker()
,从而规避黑名单检查
替换方式:
hashmap.put("class", "org.apache.dubbo.common.utils.SerializeClassChecker"); |
回到泛型处理来,规避检查后,将className
通过反射获取的类返回给type
然后对type进行类型判断进入不同的处理方法,依次判断type是否是枚举类、是否是map类的子类/是否是Object类型、是否是接口类型
if (type.isEnum()) { |
如果都不是,则直接实例化type对象,通过读取map中的键值对,通过getSetterMethod
方法获取到type对象中存在set前缀开头的key名称的方法
如果找不到type类对象中存在set前缀开头的key名称的方法,则可以设置任意field
如果能找到set前缀开头的key名称的方法,则直接通过反射获取该方法并执行
除上述执行方法外,还有一类,通过开启NATIVE_JAVA执行方式,GenericFilter#invoke方法中
方式二:java_native绕过执行原生反序列化
箭头为上述的方法,红框为使用native java方法
还有一类使用Native java方式,模式情况是禁止的,该方法是直接使用原生反序列化直接反序列化数据,ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE
值默认为false
若开启native-java执行方式,则直接调用反序列化
其中configuration
是从系统环境配置读取的信息
下面是configuration
读取系统配置的调用信息
现在需要利用一个类,能对Property
系统环境配置里的dubbo.security.serialize.generic.native-java-enable
属性设置为true
通过ConfigUtils
类存在setProperties
方法,可利用上述的泛型引用反射执行setter
方法去赋值properties
属性,将dubbo.security.serialize.generic.native-java-enable
属性设置为true
可通过Properties
类的setProperty
方法将dubbo.security.serialize.generic.native-java-enable
=true
放入hashmap
再将Properties
类对象传递入ConfigUtils
的setProperties
方法去实现修改配置属性
修改完属性就能传递payload通过java-native去执行反序列化攻击。
总结
方式一:
- 设置
generic
属性为raw.return
。 - 重新传递一个
Instance
变量值去替换重新生成new SerializeClassChecker()
序列化检查器规避黑名单 - 通过setter调用类方法,通过反射执行方法。
方式二:
- 设置
generic
属性为raw.return
。 - 将
Properties
类对象传递入ConfigUtils
的setProperties
方法去实现修改配置dubbo.security.serialize.generic.native-java-enable
属性为true
- 通过Java-native执行原生反序列化
效果区别:
方式一只能使用通过setter方法调用实现的类方法,只有利用链通过setter方法触发的才能实现,例如jdbcRowSetImpl链,通过setter和反射设置setDataSourceName为jndi加载地址,再通过setter和反射执行setAutoCommit方法实现jndi调用。
方式二可使用任意方法的序列化,兼容利用链更多,能使用更多的利用链如存在cc组件,可使用cc链进行攻击。
0x07、漏洞修复
目前官方已发布Apache Dubbo安全版本,建议尽快升级至安全版本:
Apache Dubbo 2.7.x >= 2.7.22
Apache Dubbo 3.0.x >= 3.0.14
Apache Dubbo 3.1.x >= 3.1.6
https://github.com/apache/dubbo/releases
修复方式:
- 主要新增了可序列化类判断,对type进行校验
新增checkSerializable判断和判断该类是否是可序列化的类
其中checkSerializable属性默认为true即开启序列化检查
同时在上文利用提到的方式一用的SerializeClassChecker类来规避黑名单以及方式二使用的ConfigUtils类来修改属性,这两个类都未实现序列化接口,因此都无法通过是否实现序列化的if检查
使用方式一执行SerializeClassChecker类来规避黑名单直接抛出该类不允许序列化异常
因此也自然无法使用JDBC链
0x08、参考链接
https://nox.qianxin.com/article/540
https://github.com/apache/dubbo/commit/4f664f0a3d338673f4b554230345b89c580bccbb
https://github.com/lz2y/DubboPOC
https://github.com/X1r0z/CVE-2023-23638