跳转到帖子

背景

Fastjson 的 AutoType 问题算是 Java Web 安全里被反复讨论的一类风险。很多团队对它的印象停留在“升级版本”或者“关闭 AutoType”,但在真实项目里,风险通常不是一个依赖版本号能完全说明的:历史兼容代码、第三方 SDK、网关透传、MQ 消息消费、缓存反序列化,都可能让 JSON 反序列化进入一个不容易被注意到的位置。

这篇文章主要整理一套偏防守和代码审计视角的方法:如何确认项目是否使用了 Fastjson,如何定位危险调用点,如何判断输入是否可控,以及如何给出相对稳妥的治理方案。文中不提供攻击利用流程,只讨论审计、验证和修复。

问题场景

我在做代码审计时,比较常见的场景有几类:

  • 老项目直接使用 JSON.parseObject(String) 解析 HTTP 请求体。
  • 为了兼容多态对象,业务代码开启过 AutoType 或使用了带类型信息的反序列化。
  • 消息队列消费者把消息体当成可信输入,直接反序列化成业务对象。
  • 缓存、配置中心、任务调度参数里保存了 JSON 字符串,读取后直接解析。
  • 项目本身没有显式依赖 Fastjson,但某些内部 SDK 或老版本组件传递引入了 Fastjson。

这些场景的共同点是:反序列化入口不一定来自外部 HTTP 接口,也不一定在 Controller 层。只看接口层经常会漏掉 MQ、定时任务、后台管理功能和内部 RPC。

技术分析

Fastjson 的风险核心不只是“JSON 解析”,而是“解析过程中是否允许根据输入中的类型信息实例化非预期类”。如果业务允许外部输入影响目标类型,或者开启了较宽泛的类型识别能力,就可能扩大风险面。

审计时我一般把问题拆成四个维度:

维度关注点判断方式
依赖版本是否使用历史高风险版本,是否存在传递依赖查看 Maven/Gradle 依赖树
调用方式是否存在 parseObject、parseArray、toJavaObject 等入口全局搜索调用点
输入来源输入是否来自 HTTP、MQ、RPC、缓存、配置中心结合调用链和数据流分析
类型控制是否允许输入影响反序列化类型查看 Feature、ParserConfig、目标 Class 传入方式

关键步骤

下面是一套比较实用的排查流程,适合在代码审计或版本治理时使用。

1. 确认依赖来源

先不要只看 pom.xml,很多项目是通过公共包间接引入 Fastjson 的。建议直接看完整依赖树。

mvn dependency:tree | grep -i fastjson

Gradle 项目可以使用:

./gradlew dependencies | grep -i fastjson

如果是多模块项目,建议在根目录和关键业务模块分别执行一次。审计记录里至少保留以下信息:

  • Fastjson 版本号。
  • 是直接依赖还是传递依赖。
  • 由哪个模块引入。
  • 是否存在多个版本冲突。

2. 搜索高关注调用点

常见入口可以先用文本搜索粗筛,后续再结合 IDE 或代码审计工具做调用链分析。

grep -R "JSON.parseObject" -n src
 grep -R "JSON.parseArray" -n src
 grep -R "JSONObject.parseObject" -n src
 grep -R "toJavaObject" -n src
 grep -R "ParserConfig" -n src
 grep -R "autoType" -ni src

需要注意的是,很多项目会封装一个 JsonUtil、SerializeUtil、MessageConvertor 之类的工具类,真正的危险点不一定散落在业务代码里,而是集中在这些公共方法里。审计时要把这些工具类标成重点。

3. 判断输入是否可控

不是所有 parseObject 都有同等风险。判断优先级时,可以按输入来源排序:

  • 高优先级:HTTP 请求体、请求参数、Header、Cookie、Webhook 回调。
  • 高优先级:MQ 消息体,尤其是跨系统、跨团队生产的 Topic。
  • 中优先级:配置中心、数据库字段、Redis 缓存,取决于写入权限边界。
  • 低优先级:本地静态配置、服务内部固定字符串。

这里有个经验点:内部系统的 MQ 和配置中心不要默认视为可信。很多事故里,入口并不是公网接口,而是某个低权限后台功能先写入数据,再由高权限服务异步消费。

4. 关注类型信息和宽泛反序列化

审计时重点看下面几种写法:

  • 解析为 Object.class、JSONObject.class 后再动态转换。
  • 目标类型由外部参数决定,例如从请求字段、配置项中读取类名。
  • 显式配置过 AutoType 支持。
  • 使用全局 ParserConfig 修改默认行为。
  • 为了兼容历史数据,允许解析带类型标记的 JSON。

相对安全的写法通常是:目标类型固定、字段白名单明确、输入来源有权限边界、解析后有业务校验。例如只解析成确定的 DTO,并且 DTO 字段都是基础类型或受控枚举。

5. 给出修复建议

治理方案要结合项目现实情况,不能只写一句“升级”。我一般会按短期、中期、长期三层给建议。

阶段建议说明
短期升级到维护版本,确认不存在多版本冲突先降低已知风险面
短期关闭不必要的 AutoType 能力避免输入影响实例化类型
中期替换危险封装方法对 JsonUtil 等公共入口做统一治理
中期固定反序列化目标类型禁止外部输入决定 Class
长期建立依赖准入和 SCA 检查避免历史问题回流

参考性的安全封装思路

下面是一个偏保守的封装思路:业务代码不要直接解析成 Object,也不要从外部输入拼接目标类型,而是要求调用方传入固定 Class,并在解析后做基础校验。

public final class SafeJson {
    private SafeJson() {}

    public static <T> T parse(String json, Class<T> clazz) {
        if (json == null || json.length() > 1024 * 1024) {
            throw new IllegalArgumentException("invalid json length");
        }
        if (clazz == Object.class) {
            throw new IllegalArgumentException("Object.class is not allowed");
        }
        return com.alibaba.fastjson.JSON.parseObject(json, clazz);
    }
}

这段代码不是万能修复,只是表达一个原则:公共入口应该限制输入大小、限制目标类型、避免宽泛反序列化。实际项目里还需要结合 DTO 校验、字段白名单、异常处理和日志脱敏。

审计记录模板

为了让结论可复核,建议每个风险点至少记录下面这些内容:

  • 文件路径和方法名。
  • Fastjson 调用方式。
  • 输入来源和权限边界。
  • 目标反序列化类型是否固定。
  • 是否依赖全局 ParserConfig 或 AutoType 配置。
  • 建议修复方式和影响范围。
  • 修复后验证方式。

如果团队有代码扫描流程,也可以把高风险调用点沉淀成规则。规则不一定一开始就很复杂,先把明显危险的模式拦住即可。

patterns:
  - pattern: JSON.parseObject($INPUT, Object.class)
  - pattern: JSONObject.parseObject($INPUT)
message: "Avoid broad Fastjson deserialization. Use fixed DTO class and validate input."
severity: WARNING

验证与回归

修复后不要只看编译通过,建议至少做三类验证:

  • 功能回归:确认历史 JSON 数据仍能被正确解析。
  • 边界测试:空字符串、超长 JSON、缺失字段、未知字段、类型错误。
  • 安全回归:确认不再允许外部输入影响反序列化目标类型。

日志也需要注意,解析失败时不要把完整请求体直接打到日志里。很多 JSON 里会包含 Token、手机号、邮箱、内部配置等敏感信息。建议只记录请求 ID、调用来源、异常类型和必要的字段摘要。

注意点

  • 不要把“内网接口”当作安全边界,尤其是 MQ、任务系统、配置中心这类组件。
  • 不要只升级根项目依赖,要确认打包后的最终依赖版本。
  • 不要在公共 JsonUtil 里提供过于宽泛的 parse 方法,否则业务迟早会滥用。
  • 不要允许外部参数指定 Java 类名或反序列化目标类型。
  • 不要忽略后台管理功能,很多数据写入点来自运营后台或内部工具。

总结

Fastjson AutoType 风险的治理,本质上是反序列化边界治理:输入从哪里来、能否影响类型、解析后是否有校验、依赖版本是否可控。单纯升级版本只能解决一部分问题,真正长期有效的做法,是把危险调用点收敛到少数公共入口,并在代码规范和扫描规则里持续约束。

经验上,反序列化问题最容易出现在“为了兼容临时放宽限制”的地方。临时方案如果没有回收机制,几年后就会变成审计里最难解释的风险点。
代码审计与漏洞定位示意图
代码审计、调用链与关键函数定位示意

0篇意见

推荐意见

没有意见。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
添加意见…