Java反序列化漏洞的攻击链与防御:SerialFilter应用
摘要:## 别让黑客用你的Java对象开“后门”:SerialFilter,一个开关的事 上个月,我帮一个做电商的朋友看他们服务器日志,那场面,真叫一个热闹——攻击请求像蝗虫一样,一波接一波。但有意思的是,绝大部分都被WAF和高防给拦在外面了。唯独有几条记录,…
别让黑客用你的Java对象开“后门”:SerialFilter,一个开关的事
上个月,我帮一个做电商的朋友看他们服务器日志,那场面,真叫一个热闹——攻击请求像蝗虫一样,一波接一波。但有意思的是,绝大部分都被WAF和高防给拦在外面了。唯独有几条记录,流量不大,看着平平无奇,却成功触发了后端应用的异常告警。一查,老熟人了:Java反序列化漏洞。
这玩意儿,说它是Java安全里“最熟悉的陌生人”一点不夸张。很多团队觉得,我前端有WAF,入口有高防,防火墙规则锁得死死的,还能出啥事?问题往往就出在你觉得最安全、最核心的业务逻辑里。攻击者根本不用跟你硬碰硬打流量,他只需要找到一个能往里“递话”的口子。
一、这个“链”到底是怎么连起来的?(攻击链拆解)
咱别整那些“序列化是将对象状态转换为字节流”的教科书话术。说人话就是:你的Java程序,为了把内存里一个活生生的“订单对象”存到硬盘,或者通过网络发给另一台机器,得把它“压扁”成一串二进制代码。这个过程叫序列化。反过来,读取这串代码,再“复活”成一个一模一样的订单对象,就是反序列化。
漏洞的精髓,就在于这个“复活”过程。
黑客是怎么干的呢?想象一下这个场景:
- 找个入口:你的应用里,总有一些地方会接收外部数据并尝试反序列化。可能是RMI(远程方法调用)、JMX(Java管理扩展),或者某些自定义的、接收序列化数据的HTTP接口。很多老旧系统,这种入口自己都忘了在哪。
- 精心伪造“图纸”:正常序列化数据,相当于对象的“标准施工图纸”。黑客会伪造一份“恶意图纸”。这份图纸里说:当你复活这个对象时,别老老实实只复活数据,顺便帮我执行这行代码(比如
Runtime.getRuntime().exec("rm -rf /"))。 - 利用“工具链”:Java生态里,有很多强大的公共库(比如Apache Commons Collections、Groovy、Fastjson等)。这些库里的某些类,在设计时为了灵活,提供了类似“回调函数”的机制。黑客就专门找这些类,把它们像积木一样拼接起来。你的应用里只要用到了这些库的某个版本,就等于给黑客提供了现成的“武器零件”。
- 完成致命拼接:最终,黑客构造的“恶意图纸”,就是一个由这些特殊类对象组成的、环环相扣的链条。反序列化过程,就像推倒第一块多米诺骨牌,会自动触发一连串的调用,直到执行任意命令。这就叫 “反序列化攻击链”(Gadget Chain)。
说白了,攻击者不是创造魔法,他只是发现了你家里各个房间之间连通的、没上锁的暗门,并且画出了一条能直达保险柜的路径。
二、SerialFilter:把“暗门”清单贴墙上
以前防御这玩意,那叫一个费劲。要么升级所有第三方库(牵一发动全身,搞不好就崩了),要么在反序列化时做各种复杂的校验(自己写,容易有遗漏)。
后来,从JDK 9开始,Oracle引入了一个机制,到JDK 17就更完善了,叫 “序列化过滤器”(SerialFilter)。
这东西的原理,特别像小区门口的访客登记系统。
以前:访客(序列化数据)说来找3号楼202的,保安(JVM)看都不看就放行了,访客进去后爱干嘛干嘛。 现在:保安手里有份清单(过滤器规则)。清单上写着:
- “所有来自
java.lang.*、java.util.*这些核心区的访客,登记后可以进。”(这些是可信的基础类) - “名字里带
InvokerTransformer、ProcessBuilder这种危险词的,一律不准进。”(这些是已知的攻击链零件) - “总共进来的人数(对象数量、深度、引用数)不能超过100,包裹大小(字节流长度)不能超过1M。”
SerialFilter让你能定义这份“黑白名单”和“资源限额”。
应用方式很简单,主要两种:
- JVM启动参数:
-Djdk.serialFilter='maxdepth=10;maxbytes=500000;!org.apache.commons.collections4.functors.*'。意思是:反序列化深度不超过10层,总字节不超过500k,并且直接拒绝Apache Commons Collections4里所有functors包下的类(这是著名攻击链来源)。 - 在代码里动态设置:更灵活,可以根据不同接口配置不同的过滤策略。
这招有多管用? 我见过的最直接的反馈是,一个金融项目在全局配置了严格的SerialFilter规则后,那些利用公开组件库的攻击链,100%被拦截在了反序列化动作发生之前。因为过滤器在“复活”对象的第一步——解析类名时,就发现它在黑名单上,直接抛出 InvalidClassException,游戏结束。
三、光有过滤器就够了吗?(防御的“组合拳”)
当然不是。安全从来不是“银弹”,而是“木桶”。SerialFilter是块非常关键且好用的木板,但你不能只靠它。
- 升级,永远的第一选择:如果你的JDK还在用8甚至7,啥也别说了,先规划升级到11或17以上。新版本JDK不仅自带更安全的机制,很多历史漏洞在底层就被修复了。“用新不用旧”,在安全领域是铁律。
- SerialFilter是“守门员”:一定要配!把它当成最低限度的安全基线。规则可以从严格开始,比如先禁止一切,只放行核心包和你明确知道必须的业务类,再根据日志慢慢调整。
- 断绝入口,釜底抽薪:这才是治本之策。全面审计你的应用,找出所有不必要的反序列化入口并关闭它。比如,如果不是必须,禁用RMI、JMX的远程反序列化功能。网络边界上,用WAF规则直接拦截包含序列化魔术头(
ac ed 00 05)的流量。 - 依赖库“减肥”与监控:定期用
OWASP Dependency-Check这类工具扫一扫你的项目依赖,把那些带有已知漏洞版本的三方库升级或剔除。没用的库,就是多余的攻击面。 - WAF与高防的“错位防护”:像开头我朋友那个案例,WAF防住了99%的Web攻击,但反序列化漏洞可能走的是特定TCP端口或RMI协议。这时,高防IP/高防CDN在流量清洗调度时,可以针对特定协议和端口进行深度防护,形成互补。源站隐藏(让真实IP不暴露)也能让黑客更难直接定位到你的脆弱点。
四、几句大实话
- 别指望“一键修复”:没有什么工具能点了按钮就让你绝对安全。防御是持续的过程,SerialFilter是一个极其优秀且成本低的强制约束工具,帮你把“马虎”的可能性降到最低。
- “默认拒绝”心态:在安全配置上,要养成“除非明确需要,否则一律禁止”的习惯。SerialFilter的规则设置,就是这种心态的完美体现。
- 漏洞不可怕,无知和拖延才可怕:Java反序列化漏洞的利用链(Gadget)每天都在被研究和更新。但只要你把JDK升级了、把过滤器配上了、把不必要的门关死了,你就能防住绝大多数“拿来主义”的攻击者。他们的时间也很宝贵,会优先去找那些连“访客登记”都没有的小区。
所以,如果你的Java服务还在裸奔,或者仅仅靠“隐藏”和“祈祷”来防护,今天就去看看你的JDK版本,然后把jdk.serialFilter这个参数加上吧。这可能是你今年做的,性价比最高的一次安全加固。
行了,不废话了,配规则去吧。

