Fastjson 反序列化漏洞演进、底层机理
Fastjson 是 Java 生态中广泛使用的 JSON 解析库其反序列化漏洞历经多年迭代仍未彻底根治。本文从防御视角出发系统梳理 Fastjson 各版本的安全机制演进、机制失效的根本原因、Gadget 类型分类及防御建议旨在为安全审计与架构设计提供全面参考。一、 核心防御演进与机制失效路径Fastjson 的安全演进史本质上是一场“动态类型推导”与“攻击者 Gadget 挖掘”的博弈过程。1. 1.2.24 以前无 AutoType 阶段完全反射驱动核心机制早期版本采用纯反射驱动的反序列化模型。当使用JSON.parseObject(json, Target.class)或通用的JSON.parse(json)时Fastjson 会根据 JSON 中的type键动态加载类。JSON 字符串 (含 type) └── DefaultJSONParser.parse() └── JavaBeanDeserializer.deserializer() └── MethodInvoker / FieldDeserializer └── 反射调用 target.setXxx() / getXxx()风险本质与防御缺陷无边界信任type字段可指定任意类完全信任输入内容无类白名单机制。副作用方法触发反序列化时会自动触发符合条件的Setter、特定的Getter以及无参构造函数从而触发“副作用方法”构造利用链。2. 1.2.25 - 1.2.46AutoType 初代黑名单机制防御引入引入checkAutoType(String typeName, Class? expectClass, int features)函数默认关闭AutoType支持。若开启则配合内置的denyList黑名单和acceptList白名单实现基础过滤。架构缺陷一静态黑名单的天然滞后性黑名单机制属于消极防御。Java 生态中的第三方组件如 Spring, Tomcat, Commons-Collections极其庞大黑名单永远无法全覆盖。架构缺陷二Class 缓存污染Mapping Cache 绕过这是该阶段最经典的架构漏洞。Fastjson 为了提升性能设计了类加载缓存机制TypeUtils.mappings。[输入类名] ── checkAutoType() 检查 ── (不在黑名单) ── Class.forName() 加载 │ [后续输入] ── 从 mappings 缓存直接返回 ── 注入 mappings 缓存 ──┘绕过原理攻击者通过特定的基础类如java.lang.Class触发解析诱导 Fastjson 将恶意类加载并写入mappings缓存。在随后的解析中恶意类直接跳过checkAutoType检查从缓存中被直接返回。3. 1.2.48 - 1.2.68ExpectClass期望类型引入与类型收窄防御升级修复了缓存污染漏洞并引入expectClass参数实施类型子树收窄约束$$\text{if } (\text{clazz} \not\subseteq \text{expectClass}) \longrightarrow \text{REJECT}$$绕过思路利用“广泛继承根”合法接口族当业务代码要求反序列化某个接口或基类时例如JSON.parseObject(json, Serializable.class)expectClass赋值为该接口。攻击者转向利用实现了这些广泛接口的“合规 Gadget”java.io.Serializablejava.lang.AutoCloseablejava.lang.Throwable失效根本原因安全边界 $\neq$ 类型边界。expectClass允许的子树范围过大恶意 Gadget 依然隐藏在合法的子树内部。4. 1.2.69 - 1.2.80安全模型收紧与基础设施类滥用防御演进不断扩充黑名单强化expectClass校验逐步推出SafeMode雏形。攻击面转向攻击者不再局限于常规的第三方组件漏洞转而滥用 JDK 或常用框架的“基础设施类”特殊集合类Collection/Queue/Map的特殊实现。内部工具类JDK 内部负责代理、动态绑定或特殊上下文切换的类。共性利用模型$$\text{JSON Input} \longrightarrow \text{Object Graph Construction} \longrightarrow \text{Setter/Getter Chain} \longrightarrow \text{Indirect Sink Invocation}$$5. 1.2.83 Fastjson v2SafeMode 与架构重构SafeMode 防御原理全面关闭AutoType功能不解析任何包含type的动态类强制执行严格 Schema 绑定Strict Schema Mapping。Fastjson v2 架构变革流式解析器Streaming Parser重写底层架构极大降低对反射的依赖。显式多态注册废弃全局任意类反序列化多态类必须通过JSONType(seeAlso {...})显式声明。二、 Gadget 类型分类防御与审计视角从防御视角来看不论 Gadget 如何变化其落脚点Sink主要分为以下三类1. JNDI / 远程资源加载类远程 Sink特征通过触发Context.lookup()或远程 URL 加载。典型代表com.sun.rowset.JdbcRowSetImpl。触发链setDataSourceName()$\rightarrow$setAutoCommit()$\rightarrow$InitialContext.lookup(dataSourceName)。2. 本地命令执行型OS 命令 Sink特征直接或间接调用Runtime.getRuntime().exec()或ProcessBuilder。典型代表各类利用模板类如TemplatesImpl加载恶意字节码或利用脚本引擎ScriptEngineManager执行内联代码。3. 反序列化副作用链中间桥梁 Sink特征本身不执行命令但其Setter方法会触发其他引擎的解析。分类XML 解析引擎触发 XXE / 远程对象注入表达式引擎触发 SpEL、EL、OGNL 表达式注入模板引擎触发 Velocity、FreeMarker 模版注入三、 环境对抗演进1. JDK 基线演进对漏洞利用的影响JDK 版本基线默认配置行为攻击面转移阻尼 JDK 8u121 / 7u131com.sun.jndi.rmi.object.trustURLCodebasetrue黄金时代可通过 RMI 远程加载任意恶意类。 JDK 8u191 / 7u201com.sun.jndi.ldap.object.trustURLCodebasetrueLDAP 时代RMI 受阻转而利用 LDAP 协议绕过。$\ge$ JDK 8u191远程 Codebase 默认全部关闭本地 Gadget 时代被迫转向纯本地 Gadget 链组合如利用BeanFactory触发本地命令执行。2. WAF 检测对抗绕过特征分析WAF 通常基于规则匹配攻击者常用以下机制实施绕过编码混淆利用 Fastjson 支持的字符集使用 Unicode 编码如\u0040\u0074\u0079\u0070\u0065代替type或十六进制/常规转义。结构扰动改变 JSON Key 的顺序或者插入大量无关的嵌套多层对象包装冲刷 WAF 的深度扫描限制。语法兼容路径利用 Fastjson 独有的解析特性如用,、/、{}畸形闭合等非标准 JSON 语法绕过规范的 WAF 解析器。四、 终极防御建议依赖黑名单补丁无法从根本上解决 Fastjson 的安全问题架构设计应转入深度防御模式1. 强制启用静态 Schema 绑定拒绝使用通用的JSON.parse(String text)。必须指定明确的 DTO 类JSON.parseObject(text, UserDTO.class)。禁止在 DTO 中使用Object、Serializable等宽泛类型作为属性。2. 开启 SafeMode / 升级 v2Fastjson 1.x在 JVM 启动参数中显式添加-Dfastjson.parser.safeModetrue或在代码初始化时执行ParserConfig.getGlobalInstance().setSafeMode(true)。Fastjson 2.x全面向 v2 版本迁移不开启兼容 1.x 的type解析模式。3. 语义级输入规范化与 WAF 联动解析前规范化Canonicalization在 WAF 或网关层对 JSON 进行统一的反转义与解码。AST 语义检测WAF 层的检测应基于 JSON AST抽象语法树结构检查是否存在非预期的键如type而非单纯的字符串正则表达式匹配。总结演进历程一览表阶段核心特征核心缺陷机制根本解决方向无约束反射时代( 1.2.24)完全信任输入由type反射驱动。无边界约束任意反序列化。转向静态绑定放弃“允许动态类型”的架构设计全面转向强类型 Schema 绑定与SafeMode方能从根源阻断反序列化攻击面。黑名单补丁时代(1.2.25 - 1.2.46)引入checkAutoType依赖静态黑名单 缓存。黑名单不完备缓存污染Mapping 绕过。~类型约束与安全模式时代(1.2.48)引入expectClass限制类型树逐步推行SafeMode。类型约束过宽合法接口族绕过残留语法兼容路径。~