当前位置: 首页 > news >正文

Dubbo3序列化安全机制导致的一次生产故障

前言

记录一次 Dubbo 线上故障排查和原因分析。

线上 Dubbo 消费者启动有错误日志如下,但是不影响服务启动。

java.lang.TypeNotPresentException: Type org.example.model.ThirdParam not present
...
Caused by: java.lang.ClassNotFoundException: org.example.model.ThirdParam
...

紧接着,消费者发起 RPC 调用,偶发性报错如下:

Caused by: org.apache.dubbo.remoting.RemotingException: 
Failed to send message Request [id=-7672337589162309142, version=2.0.2, twoWay=true, event=false, broken=false, mPayload=0, data=null] to /192.168.98.92:20880, 
cause: org.apache.dubbo.common.serialize.SerializationException: 
java.lang.IllegalArgumentException: [Serialization Security] 
Serialized class org.example.api.InnerParam is not in allow list. 
Current mode is `STRICT`, will disallow to deserialize it by default. 
Please add it into security/serialize.allowlist or follow FAQ to configure it.

消费者尝试重启,RPC 调用偶尔成功,偶尔失败,没有规律。

故障重现

为了重现故障,我写了个示例工程,有四个模块:

  • third-sdk

模拟依赖的三方 SDK,有两个版本:V1.0 V2.0,区别是 ThirdParam 类只在 V2.0 才提供。

  • dubbo-api

Dubbo API 模块,包含服务接口和参数类,依赖third-sdk V2.0

  • dubbo-provider

Dubbo 服务提供者,直接依赖dubbo-api,间接依赖third-sdk V2.0

  • dubbo-consumer

Dubbo 服务消费者,直接依赖dubbo-api,直接依赖third-sdk V1.0

重点:dubbo-consumer 模块自身依赖了低版本的**<font style="color:#DF2A3F;">third-sdk</font>**,ThirdParam 类是不存在的。

dubbo-api模块,IService 接口如下。InnerParam 类存在于当前模块,ThirdParam 来自三方库。

由于dubbo-consumer模块依赖的是低版本的third-sdk,所以 ThirdParam 类不存在,但只要不调用 M2 就没事。

public interface IService {String M1(InnerParam innerParam);String M2(Optional<ThirdParam> optional);
}

ServiceImpl.java和服务提供者的启动和消费者的调用均不是重点,这里不贴代码。

说明:为啥 M2 参数类型是Optional<ThirdParam>

因为如果参数类型直接是 ThirdParam,消费者启动时,解析 Service Class 这一步就会因为 Class Not Found 直接报错而退出进程。ThirdParam 必须是泛型,才不至于 Service Class 无法解析。

接着,启动 Provider,启动 Consumer,就能看到错误日志,但是不影响消费者启动。

再接着,Consumer 发起 RPC 调用,就会报错:

最快的修复方式,重构 IService.java ,方法名 M1 改为 a1

public interface IService {String a1(InnerParam innerParam);String M2(Optional<ThirdParam> optional);
}

接着,重启 Provider,Consumer。Consumer 启动依然有错误日志,但是不影响启动。

Consumer 发起 a1 的 RPC 调用,成功,不再报错。

call a1 start...
call a1 result: OK

为什么仅仅修改个方法名,RPC 调用就不再报错了呢?

故障分析

已知,Dubbo 从 3.1.6 版本开始,为了避免序列化引起的 RCE 攻击,引入了“序列化类检查机制”。只有在信任白名单里的类,才允许被序列化和反序列化。

同时,为了避免开发者手动添加白名单带来的额外负担,Dubbo 默认开启“自动信任机制”。即 Dubbo 会在 Service 暴露和引用的同时,自动信任 Service Class 依赖的相关类,这些类包括:Service Class 本身、父类和接口类型、属性类型、方法的所有入参/出参类型、异常类型等,将它们全部加入到白名单里。

根据消费者报错的信息来看,很明显提示org.example.api.InnerParam类不在白名单里面,所以序列化失败。

cause: org.apache.dubbo.common.serialize.SerializationException: 
java.lang.IllegalArgumentException: [Serialization Security] 
Serialized class org.example.api.InnerParam is not in allow list. 

由此我们推测,Dubbo 的“自动信任机制”出现了问题

通过源码我们发现,Service 在暴露和引用的时候,默认会注册 Service Class,方法是SerializeSecurityConfigurator#registerInterface

注册接口就是将 Service Class 自身、以及超类、属性类、方法的入参/出参、返回类型、异常类型等通通加入到白名单。

public synchronized void registerInterface(Class<?> clazz) {/*** 是否自动信任序列化类?默认是true* 默认会将 Service Class 涉及到的类加入白名单,全部信任*/if (!autoTrustSerializeClass) {return;}Set<Type> markedClass = new HashSet<>();/*** 1. 信任 Service Class 自身* 2. 根据 TrustSerializeClassLevel 信任所在包的层级* 3. 信任 Service Class 的接口、父类、属性类型、*/checkClass(markedClass, clazz);addToAllow(clazz.getName());Method[] methodsToExport = clazz.getMethods();// 信任 Service Class 方法的入参、出参类型、抛出的异常类型for (Method method : methodsToExport) {Class<?>[] parameterTypes = method.getParameterTypes();for (Class<?> parameterType : parameterTypes) {checkClass(markedClass, parameterType);}Type[] genericParameterTypes = method.getGenericParameterTypes();for (Type genericParameterType : genericParameterTypes) {checkType(markedClass, genericParameterType);}Class<?> returnType = method.getReturnType();checkClass(markedClass, returnType);Type genericReturnType = method.getGenericReturnType();checkType(markedClass, genericReturnType);Class<?>[] exceptionTypes = method.getExceptionTypes();for (Class<?> exceptionType : exceptionTypes) {checkClass(markedClass, exceptionType);}Type[] genericExceptionTypes = method.getGenericExceptionTypes();for (Type genericExceptionType : genericExceptionTypes) {checkType(markedClass, genericExceptionType);}}
}

Dubbo 会遍历 Service Class 所有方法,依次注册方法的入参、出参到白名单。

问题就出在这个遍历上,因为dubbo-consumer模块直接依赖了third-sdk V1.0,对于方法IService#M2(Optional<ThirdParam>)的入参,ThirdParam 类是不存在的,导致整个注册过程中断跳出,后续方法的参数都没有注册到白名单,进而导致 Consumer 发起 RPC 调用时,参数序列化报错。

另一个问题,为什么方法**IService#M1**重构为**IService#a1**,Consumer 就正常了呢?

这是因为方法签名修改后,导致Class#getMethods返回的 Method 顺序发生了改变,如果a1方法先于M2方法返回,让中断发生在a1方法注册之后,虽然整个注册过程还是会异常,但是org.example.api.InnerParam类已经添加到白名单了,对后续的 RPC 调用当然没有影响。

注意:虽然示例中通过修改方法名来改变**Class#getMethods**返回的 Method 顺序,但是强烈不建议这么做,因为 Java Doc 已经写的非常清楚了,返回的方法数组没有特定顺序,取决于JVM实现。

The elements in the returned array are not sorted and are not in any particular order.

推荐的修复方式,Service Class 所有的方法入参和出参,都不应该直接用三方 SDK 的类,这本身就不规范。在 API 模块新建 DTO 类,把三方类转换成自己的 DTO 类。

尾巴

因为消费者模块和公共 API 模块依赖的三方库版本不同,导致消费者模块缺少一部分类,进而导致消费者在注册 Service Class 方法参数到序列化白名单时,发生异常中断跳出,没有被信任的参数类,一旦序列化就会抛出异常。

又因为Class#getMethods返回的方法顺序并不固定,就会导致方法参数偶尔被信任,偶尔不被信任,所以会出现服务重启后可能又恢复正常的错觉。


文章转载自:

http://6itBn84g.mdwLg.cn
http://mV1vwIH5.mdwLg.cn
http://UeNLp8J5.mdwLg.cn
http://G3CntFFy.mdwLg.cn
http://k6HvJl9o.mdwLg.cn
http://BymHGKYP.mdwLg.cn
http://UKlgTS0e.mdwLg.cn
http://VAgUjXEa.mdwLg.cn
http://JGmnT68t.mdwLg.cn
http://YNgpFbJ6.mdwLg.cn
http://a0nJfWMY.mdwLg.cn
http://2FVnmRes.mdwLg.cn
http://ecQ26zvB.mdwLg.cn
http://e9i1tAJI.mdwLg.cn
http://9d8sFzBD.mdwLg.cn
http://4xn985Ie.mdwLg.cn
http://rHSltEnp.mdwLg.cn
http://S94BaahE.mdwLg.cn
http://Iowh3Be0.mdwLg.cn
http://oWsmCKgR.mdwLg.cn
http://ELRt16QQ.mdwLg.cn
http://YvT6RyU7.mdwLg.cn
http://mDYMbWws.mdwLg.cn
http://jp13XZpP.mdwLg.cn
http://3YyVpcKC.mdwLg.cn
http://odFEYSGN.mdwLg.cn
http://43E3QfyG.mdwLg.cn
http://ojrdhc0k.mdwLg.cn
http://1Y4wRvvI.mdwLg.cn
http://k1dKsQ6c.mdwLg.cn
http://www.dtcms.com/a/375931.html

相关文章:

  • 《2025年AI产业发展十大趋势报告》四十七
  • 传统项目管理中如何控制进度
  • C 语言第一课:hello word c
  • Cartographer 位姿推测器pose_extrapolator
  • Matlab机器人工具箱使用5 轨迹规划
  • 【git】Git 大文件推送失败问题及解决方案
  • ctfshow-web入门-php特性(二)
  • CSP认证练习题目推荐 (1)
  • MySQL 命令
  • MyBatis操作数据库——进阶
  • huggingFace学习之编码工具
  • 人工智能期末复习(部分)
  • 【Pytorch】2025 Pytorch基础入门教程(完整详细版)
  • Cookie 与 Session 的关系详解
  • Java微服务架构拆分:边界原则的实战破局与多场景案例解析
  • expect脚本详解
  • 交通识别摄像头以及带AI算法
  • SpringMVC通过注解实现全局异常处理
  • Linux基础知识(四)
  • 向量化与嵌入模型:RAG系统背后的隐形英雄
  • 你知道zip()和zip(*)怎么用吗?
  • 工业领域企业CRM常用的有哪些系统?
  • Git cherry-pick 与分支重置技术实现代码健全性保障下的提交记录精简
  • 【Nginx 运维实战】版本替换:强制 vs 平滑升级全解析
  • HTTPS加解密流程解析
  • Android 升级minSdkVersion 导致 包体积变大的处理
  • Linux系统 Python3.12版本连接达梦数据库dmPython和django_dmPython
  • 零知开源——ESP32驱动OV7670摄像头实现简易照相机系统
  • 前端开发工具trae的使用
  • Coze源码分析-资源库-创建插件-前端源码-核心组件