三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Fastjson安全模式实战:5种方法彻底解决反序列化漏洞

Fastjson安全模式实战:5种方法彻底解决反序列化漏洞

1. 项目概述:Fastjson安全模式的深度解析与实战

如果你在Java开发圈子里待过一段时间,尤其是处理过Web接口、微服务或者数据交换,那么“Fastjson”这个名字你一定不陌生。它曾经是,甚至现在依然是许多项目中处理JSON序列化与反序列化的首选工具,以其极致的性能著称。然而,伴随着高性能而来的,是一系列令人头疼的安全漏洞,尤其是反序列化漏洞(RCE),让无数开发者深夜加班应急。我经历过不止一次因为Fastjson漏洞导致的紧急升级和线上排查,那种感觉,懂的都懂。

所以,当看到“开启安全模式”这个需求时,我立刻明白这背后是无数开发者在寻求一种“治本”或至少是“强效缓解”的方案。这不仅仅是配置一个参数那么简单,它关乎到如何在享受Fastjson便利的同时,为我们的应用筑起一道坚固的防线。网上流传的“5种方法”说法各异,有些是有效的配置,有些则是特定场景下的变通,甚至有些可能已经过时。今天,我就结合自己踩过的坑和实战经验,为你系统性地拆解Fastjson安全模式的本质,并提供一套从原理到实操,再到问题排查的“典藏级”指南。无论你是正在为历史遗留系统寻找加固方案,还是在新项目中规划JSON组件的安全基线,这篇文章都将为你提供清晰的路径。

2. Fastjson安全风险与安全模式核心原理

在讨论如何开启之前,我们必须先搞清楚:Fastjson的安全风险到底从何而来,而所谓的“安全模式”又是在防御什么?

2.1 Fastjson反序列化漏洞的根源

Fastjson的反序列化漏洞,核心问题出在它为了支持复杂的Java对象图(比如包含多态、继承、内部类等)而引入的autoType机制。简单来说,当Fastjson将一段JSON字符串反序列化成Java对象时,它需要知道这个JSON对应的是哪个具体的Java类。

在默认情况下,Fastjson会尝试通过JSON中的@type字段(一个特殊的元信息)来识别目标类。例如,{"@type":"com.xxx.AttackObject", "cmd":"calc"},Fastjson会尝试去加载并实例化com.xxx.AttackObject这个类。攻击者正是利用这一点,精心构造一个@type指向某个存在于目标Classpath中、且其构造方法或setter方法存在危险操作(如Runtime.exec())的类,从而在反序列化过程中执行任意代码。

注意:这里的关键在于,攻击者指定的类必须在应用的类路径中。但现实是,很多常用的第三方库(如commons-collections, tomcat-dbcp等)中都存在这样的“危险类”(通常称为Gadget Chain),为攻击提供了丰富的素材。

2.2 安全模式(SafeMode)的设计初衷

理解了漏洞根源,安全模式的设计思路就非常清晰了:从根本上禁用或严格限制autoType功能。当安全模式开启后,Fastjson在反序列化时将不再信任JSON数据中自带的@type信息,或者只信任一个预先定义好的、非常有限的白名单。这样,即使攻击者提交了恶意的@type,Fastjson也会直接拒绝或忽略,从而切断利用链。

从Fastjson 1.2.68版本开始,官方正式引入了safemode参数。当safemode开启时,Fastjson会完全禁用autoType功能,任何包含@type的JSON字符串在反序列化时都会抛出异常。这是一个非常强力的安全开关。

但是,事情并没有那么简单。很多项目由于历史原因,业务代码中确实依赖了@type来实现一些多态特性。直接全局开启safemode可能会导致这些业务功能失效。因此,除了官方的safemode,我们还需要了解其他几种“类安全模式”的配置方法,它们通过白名单、指定解析器等方式,在安全与兼容性之间寻找平衡。

3. 五种“安全模式”开启方法深度剖析

网上常说的“5种方法”,其实可以归纳为五个不同层级和维度的安全加固策略。我将它们从“最严格”到“较灵活”进行排序和解析。

3.1 方法一:启用官方安全模式(safemode)

这是最彻底、最推荐的新建项目或可接受改造项目使用的方法。

核心原理:通过JVM启动参数或代码,设置全局开关-Dfastjson.parser.safeMode=true,使Fastjson在解析时完全禁用autoType。

具体操作

  1. JVM启动参数(推荐):这是影响范围最广、最彻底的方式。在应用启动脚本(如java -jar命令)中增加参数。

    java -Dfastjson.parser.safeMode=true -jar your-application.jar

    这样做的好处是,应用内所有使用Fastjson默认JSON.parseObject()的地方都会生效,无需修改代码。

  2. 在代码中设置(全局):在应用启动初期(如Spring Boot的@PostConstructApplicationRunner中)执行。

    import com.alibaba.fastjson.parser.ParserConfig; public class FastjsonSafeModeConfig { @PostConstruct public void init() { ParserConfig.getGlobalInstance().setSafeMode(true); } }

    效果与JVM参数基本一致。

  3. 指定单个ParserConfig:如果你有多个不同的解析配置,可以只为某个特定的ParserConfig实例开启。

    ParserConfig config = new ParserConfig(); config.setSafeMode(true); // 使用这个config来创建解析器 JSON.parseObject(jsonStr, Object.class, config, Feature.SupportAutoType);

实操心得与注意事项

  • 版本要求:确保你的Fastjson版本在1.2.68及以上。低于此版本无此参数。
  • 破坏性评估:开启前,必须全面测试现有业务。任何依赖@type进行反序列化的接口(例如接收复杂DTO,其JSON中包含@type)都会立刻报错,错误信息通常为autoType is not support
  • 不是“升级即安全”:仅仅升级Fastjson到高版本(如1.2.83/84)而不开启安全模式或配置白名单,仍然可能受到新型漏洞攻击。安全模式是独立的功能开关。
  • 如何测试是否生效:写一个简单的测试用例,尝试反序列化一个包含任意@type的JSON字符串,看是否抛出异常。

3.2 方法二:配置AutoType检查白名单

这是对方法一的补充,也是处理历史遗留代码最常用的妥协方案。当不能完全禁用autoType,但又必须使用时,白名单是唯一的安全路径。

核心原理:告诉Fastjson,只允许反序列化我明确指定的这些类,其他一律拒绝。

具体操作

  1. 使用内置白名单(Fastjson 1.2.71+):Fastjson内置了一份基础类型的白名单(如java.lang.*,java.util.*等)。可以通过-Dfastjson.parser.autoTypeAccept=com.xxx.来扩展。但这种方式不够灵活,不推荐作为主要手段。
  2. 编程方式添加白名单(主流):通过ParserConfigaddAccept()方法添加。
    import com.alibaba.fastjson.parser.ParserConfig; public class AutoTypeWhitelistConfig { @PostConstruct public void init() { ParserConfig config = ParserConfig.getGlobalInstance(); // 添加单个类 config.addAccept("com.yourcompany.dto."); // 添加包下的所有类(谨慎使用) config.addAccept("com.yourcompany.model."); // 添加一个具体的类 config.addAccept("com.thirdparty.lib.SafeClass"); // 注意:从1.2.71开始,也可以使用setAutoTypeSupport(true)配合白名单 // config.setAutoTypeSupport(true); // 开启autoType,但受白名单限制 } }

实操心得与注意事项

  • 白名单必须精确:尽量使用完整类名,或至少是公司内部确定可控的包名前缀。避免使用过于宽泛的匹配,如com.
  • 与safemode的关系:在开启了safemode的情况下,白名单是无效的。两者是互斥的。通常的选择是:要么开启safemode(最安全),要么关闭safemode但配置严格的白名单。
  • 维护成本:白名单需要随着业务类的增加而维护。这是一个持续的过程。可以考虑通过扫描项目注解或特定包路径来自动化生成白名单列表。
  • 漏洞缓解:即使配置了白名单,如果白名单内的类本身存在安全风险(可能性较小),风险依然存在。因此,白名单的安全性低于完全禁用autoType。

3.3 方法三:使用JSONType注解进行精确控制

这是一种更面向业务、更精细化的控制方法,通常与白名单结合使用。

核心原理:在需要支持多态序列化/反序列化的类上,使用@JSONType注解,明确指定其序列化时使用的typeName,以及反序列化时允许的子类。这样,Fastjson只会在这些声明的类型范围内进行autoType转换。

具体操作

// 定义一个接口或基类 @JSONType(seeAlso = {Dog.class, Cat.class}, typeName = "animal") public interface Animal { String getName(); } // 实现类 @JSONType(typeName = "dog") public class Dog implements Animal { private String name; // getter/setter } @JSONType(typeName = "cat") public class Cat implements Animal { private String name; // getter/setter } // 序列化时,会带上 @type 信息 Animal dog = new Dog(); String json = JSON.toJSONString(dog); // 结果包含 "@type":"dog" // 反序列化时,Fastjson只允许转换为 Dog 或 Cat,其他类即使@type指向它们也会被拒绝 Animal obj = JSON.parseObject(json, Animal.class);

实操心得与注意事项

  • 适用场景:非常适合业务中明确需要多态处理的领域模型。它提供了一种类型安全的autoType方式。
  • 并非全局安全策略:这个方法只对你标注了@JSONType的类生效。对于其他没有注解的类,如果JSON中包含了@type,Fastjson的行为取决于全局的safemode或白名单设置。因此,它不能替代全局安全配置,而是作为一种补充的最佳实践。
  • 可读性:通过typeName可以自定义JSON中@type的值,使序列化结果更清晰。

3.4 方法四:指定具体类型进行反序列化(最推荐的做法)

这其实是最根本、最安全的“方法”,它甚至不能算是一种“开启安全模式”的技巧,而应该成为我们编码时的黄金准则

核心原理:在调用JSON.parseObjectJSON.parseArray时,永远传入一个具体的Class对象(或TypeReference),而不是Object.class或泛化的Map/List。

错误示范(高危)

// 危险!Fastjson会尝试解析@type Object obj = JSON.parseObject(jsonStr); // 同样危险! Map map = JSON.parseObject(jsonStr, Map.class);

正确示范(安全)

// 安全:明确指定目标类型 UserDTO user = JSON.parseObject(jsonStr, UserDTO.class); // 安全:使用TypeReference处理泛型 List<UserDTO> list = JSON.parseObject(jsonStr, new TypeReference<List<UserDTO>>(){});

为什么这是最安全的?当你传入具体类型时,Fastjson的解析过程是:将JSON数据映射到已知类的属性上。即使JSON中包含了@type字段,Fastjson也会忽略它,因为目标类型已经确定,不需要autoType机制去猜测。这从根本上避免了基于@type的攻击。

实操心得与注意事项

  • 代码规范:应将“禁止使用无类型或泛化类型的Fastjson反序列化”作为团队代码规范,并通过代码扫描工具(如SonarQube, IDEA插件)来检查。
  • 接口设计:在设计对外API时,接收参数应使用明确的POJO对象,而不是Map<String, Object>Object
  • 遗留代码改造:对于历史代码中存在的JSON.parseObject(jsonStr),必须逐一排查并改造,这是加固工作中最繁琐但最关键的一步。

3.5 方法五:升级并迁移至Fastjson2

严格来说,这不是一种“开启方法”,而是一个根本性的解决方案。Fastjson2是Fastjson作者重新开发的全新版本,在架构上就考虑了安全性。

核心原理:Fastjson2默认关闭了autoType支持,并且其API设计更安全。同时,它提供了更好的性能。

具体操作

  1. 更改依赖:将项目中的fastjson依赖替换为fastjson2

    <!-- Maven --> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.64</version> <!-- 使用最新稳定版 --> </dependency>

    注意,Fastjson2的包名是com.alibaba.fastjson2,与Fastjson1的com.alibaba.fastjson不兼容。

  2. API变更适配:Fastjson2的API与Fastjson1大部分兼容,但仍有差异,需要测试。关键类是JSONJSONObjectJSONArray等。

  3. 开启Fastjson2的“安全模式”:Fastjson2同样提供了安全配置。可以通过系统属性fastjson2.parser.safeMode开启。

    java -Dfastjson2.parser.safeMode=true -jar your-app.jar

    或者在代码中:

    import com.alibaba.fastjson2.JSONFactory; JSONFactory.getDefaultObjectReaderProvider().setSafeMode(true);

实操心得与注意事项

  • 兼容性测试:升级前必须进行充分的兼容性测试。虽然API相似,但在处理日期格式、特殊字符、泛型等方面可能存在细微差别。
  • 性能提升:Fastjson2在大多数场景下性能优于Fastjson1,这也是升级的一大动力。
  • 长期主义:对于新项目,强烈建议直接使用Fastjson2。对于老项目,如果条件允许,规划迁移至Fastjson2是摆脱历史安全债务的最佳选择。
  • 不是银弹:升级到Fastjson2并开启其安全模式,相当于采用了最严格的策略。同样需要评估对现有业务的影响。

4. 五种方法对比与选型指南

为了更直观地帮助你选择,我将这五种策略总结如下表:

方法核心机制安全等级兼容性影响维护成本推荐场景
1. 官方SafeMode全局禁用autoType最高高(破坏依赖@type的业务)低(一劳永逸)新建项目、可接受改造且无@type依赖的老项目
2. AutoType白名单只允许特定类autoType中(需梳理并配置所有需autoType的类)中(需随业务维护名单)老项目,业务必须使用@type,且能明确所有需反序列化的类
3. @JSONType注解类级别声明合法子类(需结合全局配置)低(仅影响注解类)中(需为相关类添加注解)业务模型清晰,需要多态序列化的特定领域
4. 指定具体类型反序列化时跳过autoType最高(在代码层面)低(仅需修改反序列化调用点)高(需大量代码审查与修改)所有场景的黄金准则,必须逐步推行
5. 升级Fastjson2新架构默认更安全(默认关闭autoType)中(需测试API兼容性)中(一次性迁移成本)新项目首选,老项目长远规划

选型建议

  • 理想情况(新项目):直接使用Fastjson2,并在必要时开启其安全模式。
  • 老项目加固,无@type依赖:首选开启官方SafeMode(方法一),这是最彻底的方案。
  • 老项目加固,有@type依赖:采用组合策略。首先,全面推行指定具体类型(方法四)的编码规范。其次,为无法避免使用@type的场景配置严格的AutoType白名单(方法二)。可以对核心的多态模型使用@JSONType注解(方法三)进行增强。
  • 绝对禁止:在未开启SafeMode也未配置白名单的情况下,使用JSON.parseObject(jsonStr)或传入Object.class/Map.class进行反序列化。

5. 实战配置与问题排查实录

理论说完了,我们来点实际的。假设我们正在为一个Spring Boot老项目进行Fastjson安全加固。

5.1 实战配置示例

场景:项目使用Fastjson 1.2.83作为HTTP消息转换器。经排查,大部分接口使用具体DTO接收参数,但存在少数几个遗留接口使用了Map接收,并且代码中零星存在JSON.parseObject(jsonStr)的调用。

加固步骤

  1. 升级与基线配置:首先确保Fastjson升级到最新稳定版(如1.2.84)。在application.yml或启动参数中设置最严格的全局安全模式,观察服务启动和基本功能是否报错。

    # 在Spring Boot配置中,可以通过环境变量传递 # 或者在启动类中通过@PostConstruct设置

    更推荐在启动脚本中加JVM参数,这样对所有依赖都生效。

  2. 发现与处理错误:启动后,监控日志。你会看到大量autoType is not support的异常。通过日志堆栈定位到调用代码。

    • 如果是Controller接口参数反序列化报错:说明请求JSON中包含了@type,但你的DTO并不需要。这很可能是前端传递了多余的字段,或者存在攻击试探。需要检查前端代码或配置WAF规则。同时,可以将这些接口的接收参数改为具体的DTO类。
    • 如果是内部代码JSON.parseObject报错:需要分析这段代码的意图。如果它确实需要处理带@type的JSON(例如,缓存中存储了多态对象),则将此类的完整类名加入白名单。
  3. 配置白名单:在应用启动类中配置白名单。

    @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @PostConstruct public void initFastjsonSafeMode() { // 先关闭safeMode,因为我们用白名单 // ParserConfig.getGlobalInstance().setSafeMode(false); // 如果之前用JVM参数开了,这里设置无效,以JVM参数为准 // 添加白名单 ParserConfig config = ParserConfig.getGlobalInstance(); config.addAccept("com.yourcompany.project.dto."); config.addAccept("com.yourcompany.project.model."); // 添加一个明确的第三方类(如果必须) // config.addAccept("com.thirdparty.some.LegacyClass"); // 重要:如果全局safeMode已开启,白名单不生效。此时需要移除JVM的safeMode参数,改用此代码开启白名单模式。 // config.setAutoTypeSupport(true); // 1.2.71+ 配合白名单使用 } }

    如果项目用了多个ParserConfig实例,需要对每个实例进行配置。

  4. 代码扫描与改造:使用IDE的全局搜索或静态代码分析工具,查找所有JSON.parseObjectJSON.parseArray的调用。逐一检查,将目标类型为Object.classMap.classList.class等泛化类型的调用,改为具体的类型或TypeReference

5.2 常见问题排查技巧

在实施过程中,你肯定会遇到各种问题。以下是我总结的常见问题及排查思路:

问题1:开启了safeMode,但日志里没有报错,业务却不对了。

  • 排查:有些业务逻辑可能依赖反序列化后的类型信息做后续处理(例如instanceof判断)。当@type被忽略后,虽然没抛异常,但对象类型不对,导致逻辑错误。需要检查反序列化后的对象类型和业务逻辑。

问题2:配置了白名单,但依然报autoType is not support

  • 排查步骤
    1. 检查版本:确认Fastjson版本>=1.2.71,低版本对白名单支持不完善。
    2. 检查开关:确认没有同时设置-Dfastjson.parser.safeMode=true。安全模式优先级高于白名单。
    3. 检查包名:白名单配置的是包名前缀,确保完全匹配。com.xxxcom.xxx.是不同的,后者表示包下的所有类,前者只匹配类名恰好为com.xxx的类(几乎不存在)。
    4. 检查类加载器:在复杂的类加载器环境(如OSGi、Spring Boot FatJar)中,Fastjson可能无法正确加载和识别白名单中的类。可以尝试使用完整类名,并打印ParserConfig.getGlobalInstance().getAccept()的内容进行调试。

问题3:升级Fastjson2后,序列化的字段顺序变了。

  • 原因:Fastjson2为了极致性能,默认不保证字段顺序(与Jackson行为一致)。而Fastjson1默认按字段定义顺序序列化。
  • 解决
    • 如果依赖字段顺序(如生成签名),可以在序列化时指定特性JSONWriter.Feature.FieldBased(字段顺序)或JSONWriter.Feature.MapSortField(Map按Key排序)。
    String json = JSON.toJSONString(obj, JSONWriter.Feature.FieldBased);
    • 或者,在创建JSONFactory时设置默认配置。

问题4:从Fastjson1兼容模式升级到Fastjson2特定版本(如2.0.63)遇到问题。

  • 排查:Fastjson2的fastjson2.compatible=true模式旨在兼容Fastjson1的API,但并非100%。遇到问题,首先检查是否使用了Fastjson1中已被标记为过时(deprecated)或内部(internal)的API。查看Fastjson2的官方GitHub的Issue列表和发布说明,确认是否已知问题。优先考虑修改代码适配Fastjson2的标准API,而不是依赖兼容模式。

问题5:在特定国产化环境(如麒麟系统+特定JDK)升级Fastjson报错。

  • 排查:这通常是环境差异导致的。首先,确认报错信息,是否是类找不到、方法签名不匹配等。
  • 思路
    1. 使用-verbose:classJVM参数启动,检查Fastjson及其依赖的类是否正确加载。
    2. 对比该环境与开发环境的JDK版本(包括小版本)和字节码版本。
    3. 可能是该环境JDK存在某些修改,与Fastjson的某些字节码操作或反射调用不兼容。尝试升级或回退Fastjson的版本,或者尝试使用Fastjson2看是否解决。
    4. 终极方案:在相同环境中搭建一个最小化测试工程,复现问题,并逐步定位到冲突的根源。

安全加固是一个持续的过程,选择适合你当前项目阶段和团队能力的最优解,并严格执行。从今天开始,就把“指定具体类型反序列化”作为一条铁律,它能帮你避开绝大多数潜在的风险。

← 返回列表