news 2026/10/16 4:20:42

Dubbo反序列化漏洞排查与修复:从攻击链到安全加固实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Dubbo反序列化漏洞排查与修复:从攻击链到安全加固实战

那天晚上十一点多,我负责的一个内部基础服务突然被安全平台弹出一条“疑似反序列化攻击”的告警。查了代码、翻完依赖、回看了近半小时流量,确认了攻击者确实在往Dubbo暴露的端口上打恶意序列化数据包。虽然这次被平台的流量特征拦截拦住了,但整个过程让我对Apache Dubbo反序列化漏洞的成因、攻击面和修复思路有了完整认识。这篇文章就来把这次排查和修复的思考过程说清楚,适合所有在用Dubbo做RPC通信、又把服务暴露到内网甚至公网的开发者和运维同学。

先说结论:Dubbo这类反序列化漏洞之所以反复出现在漏洞预警里,不是Dubbo框架本身不安全,而是它提供的协议解析与泛化调用机制,本质上就是把“外部传入的字节流”转换成“Java对象”,而这个转换动作正好是反序列化攻击最舒服的下手点。如果你只用默认配置把服务跑起来,对端口、协议和泛化入口不加约束,一旦某个版本的官方安全补丁没跟上,被攻击者利用的风险就存在。

1. 反序列化漏洞为什么会落在Dubbo头上:三条线索的相遇

1.1 反序列化本身就是一个“读内容就得跑代码”的过程

很多人听到“反序列化漏洞”第一反应是“这不就是个格式解析的问题吗”,其实不是。Java序列化把对象变成字节流,反序列化则是把字节流变回对象。这个“变回对象”的动作并不像JSON解析那样只做字段映射,它需要根据字节流里携带的类描述信息,去当前classpath里找对应类,然后通过反射构造实例、恢复字段值。一旦字段值是另一个对象,就要递归地继续反序列化。

问题就出在这里:某些Java类在被反序列化的时候,除了恢复自身状态,还会顺带执行一些额外逻辑。比如有的类在readObject()里写了自定义恢复逻辑,有的集合类在新增元素时会调用元素的hashCode()、equals()这类方法,还有的工具类会把某个字符串当作命令或者地址去执行。攻击者只要把这些“特殊类”按一定顺序拼成一个恶意对象图,打进字节流里,反序列化时就会触发一连串调用。最终可能执行系统命令、写入文件甚至弹出一个Shell。

这就是整个攻击的本质:不是协议解析的某一个点有单点Bug,而是“Java反序列化的动态类恢复机制”配上“某个危险类”凑成了一条能执行的链条,行业内一般把这种链条叫gadget链。你可以把它理解成签收快递时,包裹上写着“拆开时自动运行包内脚本”,而Java反序列化就是那个拆包动作本身。

1.2 Dubbo把“外部输入自动变成Java对象”的操作放大了攻击面

Dubbo作为RPC框架,Provider端要接收Consumer端的请求,这个请求天然就包含接口名、方法名、参数类型和参数值。参数值怎么从二进制变成Java对象?靠的就是反序列化。默认的dubbo协议使用的是Hessian2序列化,或者你也可以把协议切到RMI、HTTP、Rest、Thrift等,但不管哪种,最终都会在某个环节执行一次反序列化动作。

关键是Dubbo对协议和序列化器的选择非常灵活。历史上有一些漏洞点集中在HTTP协议入口,攻击者构造一个带恶意序列化数据的POST请求打到对应Dubbo服务端路径上,就可能触发反序列化。还有一类问题出在泛化调用上:泛化调用允许客户端不持有接口本地类,只通过字符串指定接口名、方法名和参数信息去调用服务。如果Provider端对泛化调用的处理没有做足够校验,攻击者就能通过控制参数类型和序列化方式,把恶意序列化数据送到解析层。

所以你看,RPC框架的设计目标是把“不可信的外部网络输入”变成“可信的内部Java对象”,中间少了一道“确认对方身份”的关卡。反序列化漏洞最怕的就是这种“自动解包”的场景,Dubbo的服务端口正好把这道门敞开了。

1.3 攻击不是白给的:一条完整的利用链有固定条件

很多运维同学看到漏洞预警就慌了,觉得“我们服务是不是已经被打穿了”。其实要真正利用成功,还需要满足几个前提。

第一,攻击者要能直接或间接触达Dubbo的开放端口。端口在公网上裸露和只在内网白名单里,攻击难度完全不一样。第二,应用当前的classpath里要存在可利用的gadget类。如果项目里只引用了极少依赖,常见的利用链根本凑不齐,攻击最多让服务抛异常。第三,序列化入口不能有白名单或安全校验拦截。如果框架层面开启了反序列化安全检查,对可疑类做拦截,攻击载荷在解码阶段就会被拦住。

但要注意,即便利用不成功、服务只是报个反序列化异常,对稳定性的影响也很直接:异常流量可能让线程池被打满,反序列化过程本身也会消耗CPU和内存,形成变相的拒绝服务攻击。

2. 一条恶意调用从公网到服务端到底经历了什么:跨协议攻击复盘

2.1 攻击前置条件:一个被暴露的invoke端口

我当时处理的那次告警,问题就出在一个对外提供查询服务的Dubbo Provider上。这个Provider除了被内网业务系统调用,还因为某个历史原因被网关层转发到了外部网络,等于一个本不该暴露的TCP端口开了门。

攻击者拿到端口之后需要做的第一件事是识别协议。Dubbo协议帧是有固定格式的,端口开在那儿、响应特征也很明显,用扫描工具很容易识别出“这是一个Dubbo服务”。识别到之后,攻击者会尝试不同的调用版本和序列化类型,目的就是找一个能触发解码异常或者延迟响应的载荷,为后续真正构造利用打前站。

我们回看告警记录时发现,攻击流量最早是从一个陌生IP扫端口开始的,随后过了几分钟就出现了一批带异常字节的请求。这批请求的字节数明显比正常业务请求大,而且都指向同一个Provider服务接口路径。端口暴露的严重性就在这里:有了网络入口,才谈得上后续利用尝试。

2.2 流量怎么伪装成一次“正常业务请求”的

很多人误以为反序列化攻击流量一定长得很奇怪,一眼就能看出来。实际上恶意请求从网络层看跟正常RPC调用几乎没有区别:同样从客户端端口发出,同样以TCP三次握手建立连接,同样符合Dubbo协议帧格式。

协议帧里的核心内容是序列化ID和目标请求信息。攻击者可以按Dubbo协议规范构造一个看起来完全合法的请求:指定序列化方式为Hessian2,声明要调用的接口和方法名,然后塞入一段精心构造的对象负载。这个消息在服务端眼里就是“某个Consumer请求调用某个接口”,请求头、协议版本号、方法签名全都能对上,网关和四层防火墙根本不会丢包。

当时我们抓包回看,那批恶意请求走的甚至是已经被网关封禁的旧接口路径。也就是说,攻击者事先摸清了服务端有哪些接口暴露在外,然后挑了一个看起来合理的入口作为攻击面。这说明真正的反序列化攻击往往伴随着前期的接口枚举和指纹识别,流量特征上是一次低慢速的信息收集过程。

2.3 服务端解码链路的脆弱点在哪里

当这个“合法请求”到达Provider端时,核心链路是这样走的:协议解码器先读协议头,拿到序列化ID;然后根据序列化ID选择反序列化器;反序列化器读取请求体里的对象数据,最终调用readObject()或等价逻辑完成参数还原;再根据还原出的接口名、方法名匹配到具体Invoker去执行。

这里最危险的环节是第二步到第三步之间。Provider端默认信任“声明在协议头里的序列化方式就是真的”,同时也信任“请求体里的字节流是业务方构造的合法对象”。一旦攻击者声明的是服务端支持的序列化类型,并给出恶意字节流,解码器就会老老实实地把字节流还原成对象。这个还原过程走到某个中间类,也就是前面说的gadget类时,攻击链就被激活了。

虽然有部分版本在解码后会做一层RPC调用前的入参校验,但反序列化动作发生在校验之前。校验只负责“参数类型和接口签名匹配”,管不到“字节流里藏着什么对象”。这个顺序决定了单纯在接口层做参数校验挡不住攻击。

2.4 为什么常规防火墙和WAF挡不住这一击

防火墙是按IP和端口做白名单的,攻击者只要用的是开放端口,防火墙就认为这是合法访问。传统WAF则大多面向HTTP文本负载,如果攻击走的是Dubbo自定义TCP协议,WAF根本不知道负载里面是什么结构,更没有能力解Hessian2的二进制帧去分析gadget特征。

即便把服务用HTTP协议暴露出去,WAF要识别反序列化攻击也不容易。序列化字节流是二进制的,不同JDK版本、不同序列化器生成的字节流前缀和编码都不一样,规则库很难用一两个正则覆盖住,误报率还高。所以对RPC框架的反序列化攻击,指望外围设备兜底不现实,必须在服务端自身做安全开关,同时把网络暴露面收敛到最小。

这也是我后来处理告警时最深的体会:安全建议里常写“请勿将Dubbo端口直接暴露在公网”,不是一句空话。公网暴露等于把解码链路放在攻击者眼皮底下,任何细微的版本滞后都可能被自动化扫描盯上。

3. 三步自查:你的Dubbo服务是否也在暴露面上

3.1 第一步:核对版本与依赖,找出已知受影响范围

先别急着改代码,第一件事把所有Provider、Consumer的版本和依赖理清楚。在服务启动脚本或者管理端页面能看到dubbo版本号,但光看这个还不够,反序列化攻击往往还牵连netty、hessian-lite、fastjson、snakeyaml等间接依赖。

可以在每个服务目录下执行依赖树分析:

mvn dependency:tree -Dincludes=org.apache.dubbo:* -Dverbose

如果用的不是Maven,也可以直接查最终打的包里有没有对应版本的Dubbo相关Jar:

java -jar lib/dubbo-2.7.5.jar | head

把查到的版本和官方公告里标记为“受影响版本”的范围对比。这里有个容易漏的点:依赖传递会带出多个版本的序列化库,某个二方包内部可能绑了一个老版本Hessian或者老版本Netty,这种隐藏依赖很难通过看Dubbo版本发现,必须做全量依赖分析。

3.2 第二步:端口与协议暴露面盘点

确认端口暴露情况可以用系统自带命令扫一遍监听端口:

ss -lntup | grep java

重点看Provider的注册端口(默认20880)和各类协议对应的端口有没有绑定在0.0.0.0上。如果绑定的是0.0.0.0,说明任何能路由到这个IP的网段都能触达,再结合安全组或防火墙规则确认是否有来自非可信来源的访问。

同时还要确认三个访问入口是否存在:网关转发规则里有没有把Dubbo端口映射到公网;跳板机或运维堡垒机是否开启了端口转发;监控/注册中心所在的网络区域是否允许其他不相关服务器直接访问Provider端口。这些都是实际出现过的漏洞入口,自查时一定要逐项过。

3.3 第三步:代码审计与日志里那些“不对劲”的痕迹

代码层面重点搜两类东西。第一类是接口定义里有没有直接暴露RPC服务给外部网关的配置,比如@Service注解里把协议写成http或rest,或者把接口注册到了对外开放的注册中心。第二类是泛化调用相关的逻辑,搜一下代码里有没有GenericService、generic = "true"这类写法。

日志层面,可以按时间段搜异常关键字。反序列化攻击尝试通常会留下这些痕迹:Failed to deserialize、ClassNotFoundException、IllegalArgumentException、unexpected end of file,以及大量连续报错的WARN日志。如果发现有一段请求耗时异常飙升、同时伴随大量异常堆栈,多半有人正在做利用尝试,需要立刻把日志和流量样本留存下来。

我们当时就是靠日志检索发现,某台机器在告警前两分钟出现过一批hessian解析异常,再对照网络抓包才确认攻击者已经在测试不同载荷。这类痕迹平时容易被忽略,到了真正攻击的时候就是最重要的证据。

4. 修复执行方案:从应急补丁到彻底止血

4.1 先止血:拒绝一切非必要的泛化调用入口

如果你已经确认端口暴露在外或者网络边界无法立刻收敛,能给的第一步急控是关闭或限制泛化调用入口。Dubbo的泛化调用是反序列化利用的主要跳板之一,因为它的参数类型由调用方指定,绕过了大部分接口签名校验。

在Provider端,可以通过注册中心的配置或者启动参数把泛化调用能力关掉,具体取决于版本。大部分版本在Consumer端不设置generic参数就不会主动发起泛化调用,但Provider端要确认全局没有把generic默认打开。还要检查有没有类似“通过泛化调用兼容老接口”的历史代码,如果有,需要评估能否摘除,或者限定其只能接收白名单里的接口名和方法名。

端口和协议层面也要同时止血:不用的服务和端口当天就下掉,无法下掉的需要在安全组规则里改成只允许内网和堡垒机来源段访问。

4.2 关键修复:升级到安全版本并开启反序列化安全校验

止血之后,根因修复是升级Dubbo版本。官方每次发布反序列化相关安全通告,都会在后续版本中加入针对解码入口的校验与修复,滞后几个大版本的部署基本都属于高危暴露面。升级时要注意版本跨度不能太大,先小版本、再大版本,逐个验证兼容性。

升级完还要做一件事:把反序列化安全校验打开。部分版本的安全校验默认只记录不拦截,适合先观察后放量。可以按官方文档设置类似如下的启动参数或配置项(具体名称以你所用的Dubbo版本文档为准):

dubbo: application: name: demo-provider serialize-check-status: warn serialize-check-enable: true

warn级别会记录可疑反序列化日志但不阻断,适合灰度环境先跑一周观察误报情况;确认没有正常业务被误伤后,再切到严格模式或者配置白名单只允许业务包。

序列化方式也值得收紧。建议在协议配置里显式声明prefer-serialization,只允许你实际在用的那一种序列化器,避免攻击者用协议头切换序列化器。业务上用不到多种序列化的,就尽量不要多开。

4.3 纵深加固:为RPC流量增加白名单与访问边界

只靠升级和配置开关还不够,网络层和接入层的白名单要做起来。最基础的一条:Dubbo端口不要直接绑定在公网网卡上。让所有外部流量先经过网关和审计链路,再转发到内网Provider。安全组规则明确只放通需要调用的Consumer网段,并定期核查安全组是否有“顺手放行”的冗余规则。

如果你的场景里RPC也要经过网关,网关层要加接口级别的授权校验。每个外部请求带着身份凭证进来,在转发到Provider之前就完成认证,连“尝试触达Dubbo端口”的机会都不给。实际配置的时候,建议优先开放最小必要端口,并且对每个端口备注清楚用途、负责人、过期时间。

还有一个很容易被忽略的点:Provider和注册中心之间也要隔离访问。注册中心如果随意可访问,攻击者可以通过它枚举已注册的Provider列表,精准定位攻击目标。注册中心的管理端口和API最好只允许运维网段访问。

4.4 修复后的验证:用一份无害探针确认漏洞真正闭环

修复完不能直接宣布“收工”,要做一个可复现的验证。我当时做了两件事。

第一,用正常业务流量跑一遍核心链路的RPC调用,确认升级和配置变更没有影响接口参数的反序列化。这一步主要看日志里有没有新的serialize-check报错,如果正常对象被误判成危险类,说明白名单配置粒度太粗了,需要按业务包名细化。

第二,构造一个无害的探测请求,声明一个当前classpath中不存在的类,请求体里只带一个普通对象,不包含任何真实命令执行载荷。目的是验证反序列化安全校验链路是否会拦下这类异常类名。如果日志出现一条可疑类拦截记录,说明校验入口已生效;如果请求直接通过并回调了业务方法,说明安全开关其实没起作用,要继续查配置加载情况。

验证过程一定要记录版本号、配置项、探针请求体和对应日志片段,存成回归文档。否则过两个迭代,配置被新版本覆盖了或者安全开关被误关了,没人会记得当时的基线。

5. 修复不是终点:把“漏洞预警”变成常态化防线

5.1 每次发版前做一次“反序列化位置”扫描

经历一次告警之后,我养成了一个习惯:发版前默认做一次反序列化风险扫描。扫描内容不复杂,就是一个检查清单。

第一,依赖清单里有几个序列化相关组件、版本号是多少;第二次,代码里有多少个泛化调用的入口;第三次,协议配置显式声明了几种序列化器和几个监听端口;第四次,安全组和防火墙规则里,对外端口是否有变化。把这个清单做成脚本或者在CI流水线里跑一遍,有变动就强制走安全评审,能省掉很多上线后查漏洞的时间。

依赖扫描建议直接用仓库提供的依赖漏洞检测能力,每个季度扫一次全量项目,重点关注序列化组件和Netty这类基础网络库的版本更新。RPC框架的稳定版本往往还会发布紧急补丁,迭代里新增的依赖很容易把老版本组件又带回来,这种回归问题很常见。

5.2 常态化监控与应急SOP

监控层面要加两类指标:一类是Dubbo服务端反序列化异常计数器,另一类是解码失败请求的来源IP聚合。安全平台的特征库能挡住大部分恶意流量,但规则总有滞后,自己服务端的异常指标是最直接的第一信号。

应急的时候按这个顺序走:发现异常反序列化告警后,先截断来源IP到Provider的访问通道,再把涉事节点从负载均衡摘掉,同时保留完整抓包和日志;然后确认是否存在已知利用链,逐台核对版本和配置;修复完成后用探针复测,最后复盘整个暴露面。每一步都做记录,不做记录等于白应急。

我还做过一个更“笨”但有效的做法:把序列化白名单需要允许包名列成清单,发版时逐项核对。很多反序列化漏洞利用载荷都指向框架依赖里的工具类,如果业务自己的包名占了绝大多数白名单空间,攻击者可选的类范围就会小很多。这比依赖某个WAF规则靠谱得多。

最后,修复这类漏洞最大的经验是:别把“升级一次”当终点。Dubbo反序列化漏洞的形态一直在变,入口可能从HTTP协议挪到泛化调用,又从泛化调用挪到某个序列化器的特性上。保持端口收敛、关闭泛化调用、开启安全校验、定期扫依赖,这套组合打下来,就算再有新的反序列化预警出现,你也能在半小时内完成排查和响应,而不是等到攻击者扫到门口才想起去看一眼自己开的端口。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 4:20:37

Python全流程租房数据智能分析平台:爬虫、K-means与Django实战

租房市场的核心矛盾,是信息极度分散且真假混杂。同一个城市,不同平台上同一套房的报价能差出 15%,同小区不同户型的租金构成逻辑也完全不一样。我做这个“Python 全流程租房数据智能分析平台”的初衷,就是想用一套自动化链路把“采…

作者头像 李华
网站建设 2026/10/16 4:20:10

Linux下MySQL二进制包安装配置实战:从环境准备到常见问题排查

说实话,MySQL的安装配置算是我见过最容易被新手嫌弃“教程没用”的一个领域了。不是教程写得差,是环境千差万别。别人一条命令就能装完,你这边却各种报错;别人解压完就能启动,你这边不是缺依赖就是权限不对。我自己当年…

作者头像 李华
网站建设 2026/10/16 4:19:18

AI大模型应用高效指南:任务拆解与上下文管理实战

1. 先想清楚:AI大模型应用“高效”问题的本质我接触AI大模型应用这几年,有一个特别明显的感受:同样一个模型、一套工具,在不同人手里用出来的效果天差地别。有人花十分钟拿到一份可以直接交付的方案,有人折腾一小时还在…

作者头像 李华
网站建设 2026/10/16 4:19:05

用 VSCode 开发 Spring Boot:环境搭建、热部署与调试实战指南

作为一个平时主力用VSCode写Java的人,我经常被人问:正经后端开发不都用 IDE 吗,你折腾 VSCode 图啥?说实话这个问题我答过很多次,但每次都能聊出点新东西。这次干脆写成一篇完整的记录,聊聊用 VSCode 开发 …

作者头像 李华
网站建设 2026/10/16 4:18:44

上下文工程实战指南:从提示词到记忆管理的完整方法论

做AI应用落地这些年,我见过太多类似的场面:同一个基础模型,A团队调出来的系统像个干了五年的老师傅,话术得体、边界清楚、引用准确;B团队调出来的却像第一天上班的新人,答非所问、张嘴就跑火车。差别通常不…

作者头像 李华
网站建设 2026/10/16 4:17:54

STM32 HAL库实战:DHT11温湿度采集与OLED显示驱动移植

简介:这是一份面向嵌入式与物联网初学者的STM32实战工程,基于HAL库实现DHT11温湿度采集与OLED实时显示,适合想打通传感器驱动与显示交互的开发者练手。压缩包共193个文件,约6.9MB,以h头文件、c源文件、o目标文件、crf编…

作者头像 李华