Fastjson 反序列化漏洞这几年基本成了 Java 后端面试和故障排查里的“必考题”。动态语言和静态语言的边界本来就有很多模糊地带Fastjson 又是出了名的性能狂早期几乎每个电商系统里都能看到它的身影。但高性能的代价往往是安全设计上的放松autoType 机制让攻击者有机会通过一个看起来平平无奇的 JSON 字符串最终让服务器跑起恶意代码。我不打算再复述一遍漏洞时间线而是从一个后端开发和安全运维的双重视角把 Fastjson 反序列化问题从头到尾拆开说清楚它为什么危险、攻击链是怎么走的、以及真正落到项目里该怎么防御和排查。看完这篇你至少应该能回答三个问题Fastjson 漏洞的根源是什么、自检你的系统是否存在风险、以及为什么“升级版本”不能让你高枕无忧。这篇内容适合 Java 后端开发、安全测试、以及正在维护老系统的运维朋友。你要是刚接触反序列化攻击也能从上手部分建立整体框架已经有经验的可以直接跳到第三节的加固方案和第四节的排查实录那里有大量我在实际接口里踩过的坑。1. 为什么 Fastjson 会成为安全重灾区核心设计思路拆解1.1 性能与便利的背后藏着什么代价当时流行一个说法Fastjson 的性能能比常规 JSON 库快出好几倍尤其在复杂对象转换上API 又极其简单一两行代码就能完成 Java 对象和 JSON 的互转。在业务接口多、并发量高的项目里这种优势天然吸引人。工程团队往往只看单次压测的 QPS 提升却很少关注它在安全层面所作的取舍。Fastjson 有一个明显的“讨好开发者”的特性就是 autoType。它允许在 JSON 字符串中直接携带 Java 类名反序列化时由 Fastjson 自动把内容绑定到对应类型上。这个机制非常方便比如你有多个子类希望通过一个接口接收不同结构的数据只要传一个type字段就能自动创建对应的类。代码写起来爽了但也等于把“实例化哪个类”的决策权交给了用户输入。你可以类比快递柜的逻辑正常情况下快递员知道哪一格里放什么尺寸应该存到几号柜但如果柜子允许用户在输入框里直接指定“放 3 号柜超大格”而 3 号柜又能开启内部通道那用户就不仅能决定自己的包裹放哪还可能借机渗透到管理后台。Fastjson 的问题就是这样它给了用户一个默认立即可用的“类型开关”却没在早期做好边界控制。1.2 为什么传统防御思路在它面前总是失效传统 Web 安全的防御重点是 OWASP 前十名里的注入、越权、XSS而反序列化问题更隐蔽它在 JVM 体系里普遍存在不只 Fastjson。但 Fastjson 之所以成为重灾区还因为它有一套非常复杂的“类查找链”——先解析 JSON 的语法节点再去触发 setter、getter、构造方法等多个调用点任何一个地方设计不当都可能被攻击者利用。而黑名单思路更是天然有漏。从早期常见类的封禁到后来变成长长的黑名单列表攻击者永远在寻找未收录的类。黑名单只能防已知不能防未知这是机制层面的缺陷。所以即使官方不停地补补丁安全圈依然认为只要默认开启 autoType 就存在不确定性风险。这也是后来社区建议直接关闭 autoType、或者使用全局白名单的根本原因。2. 反序列化漏洞原理深度剖析从 JSON 请求到恶意代码的链路2.1 反序列化过程到底做了什么抛开攻击视角先从正常业务说。一个 User 对象有姓名和年龄Fastjson 接收到{name:张三,age:25}后按 User 类的字段声明把值填充进去然后你的业务代码可以正常使用这个对象。这就是常规反序列化安全的。但 Fastjson 的Parse方法远远没有这么单纯。它内部会解析出当前目标类型然后根据类结构去寻找 setter 方法为每个字段调用对应 setter。攻击者如果控制 JSON 内容就等于在控制这个对象初始化过程中的每次方法调用。他们不需要修改服务器代码只要找到一个“副作用危险”的类——比如能读取特定文件、能进行网络连接、能执行远程加载——就可以在 JSON 中构造合适的字段让这些调用组合起来形成一条完整的攻击链条。这个过程放到代码里往往就是一行JSON.parseObject(input)。很多开发者在写接口时毫不在意地直接调用根本没想到输入体量稍微变化代码执行就完全不同了。2.2 JNDI 注入把危险动作搬到远程反序列化漏洞要变成真正的远程代码执行通常需要借用 JNDI 这个 Java 服务接口。JNDI 本身是个好东西它用来统一查找命名目录服务例如数据源、消息队列、远程对象。其中有一个能力是可以通过JNDI动态加载远程类。这就打开了重要的攻击窗口。攻击者的思路大致是找一个在 JNDI 初始化过程中能触发外部网络请求的类让 Fastjson 反序列化时创建它然后服务器就会根据攻击者传入的地址发起一个“类获取”请求拉取恶意对象并实例化。完整攻击流程从外部看只是一次普通的请求但实际已经替你执行了远程代码。这里我不再展开具体类和 Payload因为给出可复现的攻击代码对防御没有帮助反而会让这篇变成攻击指导。如果你在案发日志里看到某个 JSON 中包含以rmi、ldap等协议开头的地址基本就能判断有人在尝试 JNDI 注入。最终是否成功取决于你的 JDK 版本、Fastjson 版本、是否存在对应安全过滤组件。2.3 类加载审计安全团队看到的是一个“动态行为”很多开发者以为“我只要不让用户传恶意类名就行”但反序列化攻击的抽象程度比这高得多。Fastjson 在运行时要通过目标类名做类加载攻击者“构造”的就是这一条类加载路径。安全监控中我们看到的关键行为包括出现异常类的初始化、setter 中产生外部连接、JSON 中携带非预期的type字段。这也是为什么我在实际项目里并不只依赖单纯的黑名单过滤。我会在反序列化层设置一层开启 Shut 记录和审计机制把所有包含类型标志的 JSON 请求做一次快照。一旦某个类名不在业务预期内直接阻断。黑名单是告诉你什么不能做白名单是只允许什么可以做后者更适合作为安全底线。3. 实战视角下的风险识别、检测与加固不能只靠升级版本3.1 系统自检三步看清你的库存量第一步是找依赖引用。在 Java 项目中Fastjson 通常被声明为 Maven 或 Gradle 依赖。你可以用项目侧的依赖清单命令看它是否有传递依赖这个清单一出哪些模块引用了 Fastjson、版本是多少就一目了然。很多老系统会把 Fastjson 塞在某一个公共模块里再透过各种“底层依赖”传递到你的业务模块自己还没意识到。第二步是识别版本风险。Fastjson 的 bug 修复节奏非常密安全团队建议尽量停留在某一个安全版本而不是历史上某个“当时还好”的版本。你可以通过官方安全公告列表对照自己的版本确认它是否具有已知风险点。如果版本早于官方明确关闭 autoType 的版本那就要拉响警报。第三步是确认代码里的写法。如果项目里大量出现JSON.parseObject后强转成 Object或者直接将请求 body 映射为 Object 再下游转换风险就非常大。反而代码里都写清楚了具体返回类型只用了 Fastjson 做基本序列化风险相对低得多。3.2 配置加固实践白名单设置与 autoType 关闭为了把这个问题掰到最直观层面我直接写一段市面上普遍认同的加固方案。侧重点在于调整 Fastjson 的ParseConfig让它转而走受限模式。import com.alibaba.fastjson.parser.ParserConfig; ParserConfig config new ParserConfig(); // 关闭 autoType禁止通过 JSON 中的类型信息自动创建类 config.setAutoTypeSupport(false); // 理论上这个开关已足够挡住绝大多数新旧漏洞利用链 // 如果业务确实需要多态就改成全局白名单除了特定包名和类名之外全部拒绝 config.addAccept(com.yourcompany.obj.);看到这里很多同学会问直接设置config.setAutoTypeSupport(false)不就好了是的对于大部分业务场景这是一个有效的兜底操作。难点在于项目里如果多个地方都直接使用静态方法JSON.parseObject它们读的是 Fastjson 默认的全局配置。因此你还需要在启动阶段设置全局配置ParserConfig.getGlobalInstance().setAutoTypeSupport(false);还有个隐蔽的坑老代码里可能在某处手动设置了ParserConfig.getGlobalInstance().addAccept(...)来兼容业务这个兼容动作又会把 autoType 重新打开。排查时记得全局搜索有没有类似addAccept、autoTypeSupport的地方不要只放一两个新配置结果被老代码盖回去。我自己就曾困惑过“为什么配置重启后还是弹 autoType 默认开启”最后发现是某个历史兼容用的addAccept把开关顶上去了。3.3 入口拦截在还能拦截前拦截配置层面做好了不代表就可以把安全问题全部交给反序列化库。业务入口应当再做一层判断凡是用户传入 JSON 包含type字段的请求一定 99% 是异常的。普通业务根本不该这么传。你可以做一个前置过滤器在所有请求进入 Controller 之前先检查原始请求体如果检测到type字符串就直接返回错误提示并记录日志。这里要注意不要只做简单字符串包含判断还要考虑 JSON 里属性名有可能被 URL 编码或者带空白所以最好基于 JSON 解析树再去读 key但代价是你会多一次 JSON 解析性能有损耗。基于安全优先的原则我建议在小流量系统或者关键接口上做这一步。如果你们团队还在用老的 Fastjson 版本又暂时没有升级计划那么这层入口拦截是唯一能兜底的防线。我自己在实际项目里遇见的最令人后背发凉的情况就是同一个接口既接收了type又恰好存在一个可用于 JNDI 回连的类。4. 常见反序列化问题排查与避坑实录4.1 日志里的异常不等于“已被利用”很多 Java 开发会问我在日志里看到了ClassNotFoundException或者autoType is not support是不是已经被攻击了其实不完全是。这两种情况往往意味着攻击尝试被挡下了一半。autoType is not support是说明 Fastjson 已经拒绝加载未知类型没有让它继续走危险路径而ClassNotFoundException也可能是攻击者乱填了一个类名目标系统根本不存在那个类。真正的“已被利用”通常具备更强烈的特征存在出方向的 JNDI 请求或者逻辑代码中本来不该出现的类被创建。排查时你可以把日志里所有异常类名拉出来和业务代码中的包名做一个交集大概率就能区分误报与真实攻击。4.2 我常用的“修复三连”我在各种项目里总结了一套维修清单对付 Fastjson 这类问题基本够用。第一升级到稳定版本。这里要特别注意别盲目追最新版。最新版本发布初期的兼容性问题可能让业务直接炸你要选择至少已经在生产环境运行过一段时间的版本同时参考官方公告确认它修复了哪些已知漏洞。第二全局关闭 autoType。如果业务多态需求强烈可以换成更现代化的方案。不要为了让 Fastjson 支持动态类型就在全局打开这个开关宁可迁移到别的序列化框架或者升级方式用白名单。第三改造“万能 Object”链路。把所有JSON.parseObject(input, Object.class)的写法都改掉。这类写法不仅不安全还让代码变得极难维护一旦接口入参结构变了排查问题如同大海捞针。每多一个具体返回类型就把攻击面缩小一部分代码可读性顺带提高一个等级。4.3 一个从事故到整改的真实场景复盘我曾参与处理一个某支付项目的安全整改。历史接口里有一个通用回调入口会接受任何方传来的 JSON 字符串然后反序列化成 Object 后丢给一个策略分发器让它在运行时根据类型做处理。这个设计初衷是想兼容多种回调类型但做安全评估时发现完全就是裸奔状态。首先定义了一组 DTO 类把这些回调类型收敛成有限的几种然后在网关层增加白名单只允许这几种 DTO 存在最后对入口请求做 JSON 深度校验所有非预期字段直接拒绝。整改之后系统连续运行几个月再没有出现过异常类名告警性能因为少了反射反而更好。这个案例我想说明的是不要想着用一个“灵丹妙药”式配置彻底解决安全整改要落在数据入口和代码结构上而不是靠某个框架的某一个开关单打独斗。4.4 上线前的快速安全自查表如果你正好要发一个用到 Fastjson 的版本以下清单可以帮我快速肝硬化一次风险。通常我会在发布前把一个测试样例发到预发环境检查它是否被正常拦截。测试样例本身没有危害只是模拟type字段或者常见远程类名的输入。检查项操作通过标准依赖版本用依赖扫描工具确认 Fastjson 版本不在已知高危清单内autoType 配置全局搜索addAccept/setAutoTypeSupport非多态场景应保持关闭入口过滤发送含type的测试 JSON返回拦截或报错不落入业务逻辑代码写法搜索parseObject(..., Object.class)无纯 Object 接收逻辑日志监控配置异常类名告警出现即通知安全小组依赖清理查找所有传递依赖路径未被低版本间接引入这张表里每一项都很朴素但组合起来能解决绝大多数反序列化风险。我更推荐把它固化到团队的发布流程里而不是等安全找人时再补做。5. 检测与利用的边界安全研究者该如何“正当练手”5.1 利用检测脚本的伦理界限标题里写着“实战利用”让我把这一点说得明确一点你可以用 Fastjson 历史漏洞去理解攻击原理在隔离的测试环境里做验证但绝不建议直接用生产系统做攻击测试更不该在工作以外的地方传播可用的利用代码。安全研究者的价值在于帮忙捡漏、加固、提升整体防护水位而不是当攻击者的导航。如果你是安全测试人员建议用一套脚本检查系统是否“允许”危险类名的加载而不是直接跑恶意类。比如构造一个简单的 JSON 字符串在 field 中包含rmi地址看系统是否发出了外部连接请求。这个验证只触发网络请求不执行真实攻击代码既安全又能证明风险存在。5.2 搭建分析环境的最低配置想快速上手熟悉漏洞特征建议在一个独立容器环境里做。最简配置就是 JDK、Fastjson 目标版本、一个简单的 HTTP server然后抓包看它是否发起了对外的 LDAP/RMI 连接。不要在你的集成开发环境里随意跑最好连网络环境都隔离。很多新手一上来就找现成的漏洞利用工具结果输入的类名写错了报了各种各样异常也没理解底层发生了什么。我倒建议你从最小例子开始自己遮罩一个恶意类用来观察 Fastjson 反序列化过程中 setter 和 getter 的调用顺序。只要搞清楚了调用顺序你对整个问题的认知就不再是背文章而是真正知道为什么某段代码会打开后门。5.3 跟踪补丁情报与社区趋势Fastjson 漏洞情报通常会频繁出现在安全议题和各类推送里。你要在意的是这类情报的细节是否涉及代码层机制而不是只看谁又被爆出了新漏洞。我看我身边很多人的习惯是看到标题就转发到群里补充一下“明天要升级”结果是版本升错还引发连锁故障。更好的方式是订阅官方仓库的 release 页面关注版本发版说明里有没有反序列化相关的关键字以及社区对这次修复的验证结果。无论你是开发还是安全一旦发现现有系统在受影响范围内别等到大促前才行动。提前在测试环境完成升级和回归比到底线时刻被叫起来截今晚的服务要好受得多。6. 反序列化攻击之外的延伸思考6.1 变更不只是排查一次漏洞处理完 Fastjson 问题之后我建议把你系统里其他 Java 反序列化入口一并排查一遍因为同类问题往往不止一处。很多项目用了不只一套工具库做 JSON 转换例如另一个常见的 Jackson 也有过类似问题但配置方式和 Fastjson 不一样。你需要稍微建立起一个“反序列化即时清单”的框架——只要涉及把一个字符串还原成对象都需要考虑类加载的安全边界。6.2 序列化协议的选择建议分层来看如果不需要多态反序列化直接用严格类型绑定最安全。Java 本身有原生Serializable但也出过反复被利用的历史漏洞如果只是为了配置 JSON给前端用建议改成一个默认不过分灵活、配置不那么花哨的解析库。大部分业务代码并不需要动态类型安全的“默认设置”反而能强迫你写得更清晰。当你某天读到一篇新的反序列化漏洞文章时希望第一反应不是“又来了”而是去翻一下自己的项目依赖树确认各种解析库的边界点都有被正确收敛。我自己在参与过几次真实安全事件处置后几乎把所有内部服务都排查成用一个库、一种配置、一个网关入口而不是不同团队各自看图全用各种方便的方法。现在再回头看 Fastjson 反序列化漏洞它更像是一次警钟性能优化永远可以后补但安全边界从第一天就要画好。希望这篇文章能帮你把这条边界重新捋一遍。你手头如果也有遗留的老接口这周就值得抽空做一次“反序列化体检”。
阅读完成 · 觉得有帮助?