Fastjson 1.2.83 RCE(中):如何生产 Hacking Trick

0. 引言

2130706433 = 127.0.0.1。Java 的 URL 解析接受十进制整数形式的 IP,这个行为源自 4.2BSD 的 inet_aton,距今四十年。

在 fastjson 1.2.83 的利用链里,checkAutoType 会把 @type 值中的 . 替换为 /,点分 IP 当场被毁,整数 IP 原样存活。于是 jar:http://2130706433:18081/x!/poc 成了一个可用的远程类地址。

一个四十年前的解析器行为,被一个防御措施激活,变成了利用链上的关键一环。

上篇里我们把这条链从头到尾拆了一遍。这条链上有一串 trick:整数 IP、/proc/self/fd、polyglot JAR、ASM 非法类名、unicode @type 编码。

漏洞分析里到处是"trick"这个词,但它从来不是一个可以照着执行的清单。"这里需要一个 trick"——然后呢?去哪找,找到了怎么验证,验证了怎么留给下一个人复用?这个词一个都不回答。

其他领域有现成的答案。二进制安全把 gadget 做成了工业:ROPgadget 这类工具机械扫描代码段,找 gadget 不需要灵感。行为层能不能做同样的事?这篇文章用五个 trick 当样本,拆开看它们分别利用了什么、有什么共同结构、能不能系统地找。

1. 五个 Trick

1. 整数 IP

Java 的 InetAddressURL 解析接受十进制整数形式的 IP。2130706433 = 0x7F000001 = 127.0.0.1。这个行为继承自 BSD inet_aton,规范除了点分四段还允许点分两段、点分三段、单个十进制数、十六进制数等写法,存在四十年了。

上篇的利用链里,checkAutoType@type 值的 . 全替换成 /,点分 IP 当场被摧毁,整数 IP 原样存活。于是 jar:http://2130706433:18081/x!/poc 成了一个可用的远程类地址。

它的 Gap 夹在 URL 解析的规范行为和开发者"IP 就是点分四段"的默认假设之间。输入经过了一个会破坏点号的字符串变换,但你仍然需要表达含点的标识符——这时候该想起它。

2. /proc/self/fd

Linux 把进程打开的文件描述符暴露在文件系统里,/proc/self/fd/N 是 fd N 的路径化身。这个机制本来是为 lsof 等诊断工具设计的,但任何接受本地文件路径的 API 都能用它。

fastjson 案中,JDK 的 URLJarFile.retrieve 把远程 JAR 落盘为匿名临时文件(jar_cache*),路径不可知,但 fd 号可以枚举。jar:file:/proc/self/fd/N!/poc 绕过了"路径必须可知"的限制。

Gap 出在同一个接口的两种用法之间:诊断通道变成了寻址通道。这个 trick 出身很老——PHP LFI 时代就是社区知识,CTF 常规武器,最近被组合进 Java jar:file: 链。当你需要把一个刚写入的、匿名的、路径不可知的临时文件变成可寻址的路径时,该想起它。

3. polyglot JAR

zip 格式从文件尾部定位中央目录,JDK 的 ZipFile 实现容忍前缀垃圾。PNG、GIF 等图片格式则从头部解析。两种解析方向相反,一个文件可以同时是合法图片和合法 JAR——这就是 GIFAR 的思路(2008 年前后)。

利用链里,上传通道只验图片头,攻击者送入的却是带恶意 class 的 JAR。无出网场景下,上传 polyglot JAR 加 jar:file: 本地路径可以构成 staging 链(这个方向还没有完整实测,见上篇 E1/E2)。

Gap 在两个 parser 对同一字节流的解析方向不同:zip 从尾部读,图片从头读。需要把受控字节塞进一个类型受检的通道时,该想起它。

4. ASM 非法类名

Java 语言的标识符语法(javac 强制)远窄于 classfile 里非限定名的规则。JVMS §4.2 的禁止集只有 . ; [ /(以及方法名的 < >)。含 :! 的类名任何 javac 都编译不出来,但 JVM 接受,ASM 可以直接写这样的字节码。

fastjson 案中,恶意类的名字本身就是一个 URL(jar:http://2130706433:18081/x!/poc),必须用 ASM 生成。

这个 trick 的 Gap 甚至不在两个组件之间,而在同一厂商的两份文档之间:Java 语言规范和 JVM 规范,两份不相洽的权威文本。五个 trick 里最纯粹的一个。需要构造语言语法不允许、但运行时接受的标识符时,该想起它。

5. unicode @type

JSON 字符串允许 \uXXXX 转义。fastjson 案中,WAF 按字面特征匹配 "@type",fastjson 在解析时把 "@type" 解码为 "@type"。检查发生在解码前,生效发生在解码后。

Gap 在规范化的时点:WAF 看到的是字面,fastjson 看到的是解码结果。通道上有基于字面/模式的检查、而下游 parser 存在解码或规范化步骤时,该想起它。

2. 共同结构

把五个 trick 的 Gap 列在一起看:

trick 语义 A 语义 B
整数 IP URL spec(inet_aton 遗产) 开发者假设(IP=点分四段)
/proc/self/fd 意图用途(诊断接口) 可达用途(路径化身)
polyglot JAR zip spec(从尾部定位) JDK 实现(容忍前缀垃圾)
ASM 非法类名 Java 语言规范 JVM 规范
unicode @type WAF 的 JSON fastjson 的 JSON

没有一例是代码写错了。每一例都是两份各自成立的语义,在某个交叉点上不兼容。

这个概念在二进制安全里已经有成熟的对应用:gadget。ROP/JOP 里,gadget 是从已有代码中回收、按功能索引、跨场景复用的指令片段。ROPgadget 这类工具可以机械扫描代码段找 gadget,不需要灵感。

trick 就是行为层的 gadget。跟二进制 gadget 的区别是:ROPgadget 扫的是代码段(静态的),行为层扫的是组件的行为特征(要跑起来才知道)。但思路是一样的:从已有的东西里回收可复用的资产,按场景索引。

换句话说:trick 是一段被武器化的语义分歧,从原始上下文中抽象出来、命名、按约束模式索引,成为可跨场景复用的资产。

3. Trick 是怎么来的

把五个 trick 的产生过程考古一遍,最明显的发现是时间错位:使 trick 成立的行为事实,远早于 trick 本身。

3.1 行为先于 trick

trick 行为事实存在 被武器化
整数 IP inet_aton 接受整数形式,4.2BSD(约 1983) SSRF 过滤绕过(2010s)
/proc/self/fd Linux procfs 暴露 fd(九十年代初) PHP LFI(2000s);Java 链(2026)
polyglot JAR zip 尾部目录(1989)+ JDK 容忍前缀垃圾(1997) GIFAR(2008)
ASM 非法类名 JVMS §4.2 与 JLS 标识符规则分歧(1996) fastjson 链(2026)
unicode @type JSON \uXXXX 转义(自有规范起) WAF 字面匹配绕过(2010s)

行为事实先于武器化存在,多数早十年以上。攻击者造不出 inet_aton 的宽容,也造不出 JVM 对非法类名的接受。这些行为是出厂即有的,和任何攻击意图无关。本文把它们叫做 feat

feat 层是可以机械扫描的。二进制世界已经示范过了:ROPgadget 不模拟黑客找 gadget 的认知过程,它绕开认知直接扫代码段。feat 扫描是同一性质的操作:读配置、起一次服务、枚举 handler,不需要理解攻击意图。

3.2 约束催生 trick

feat 一直在那里,但变成 trick 需要一个触发条件:约束。

整数 IP 当了四十年解析器冷知识。它变成 trick,是因为"输入经过摧毁点号的变换、但仍需表达含点标识符"这个约束出现了。约束从哪来?

trick 约束 约束的制造者
整数 IP 点号被 replace 摧毁,但需表达含点标识符 fastjson checkAutoType,防御机制的组成部分
unicode @type 通道上有字面匹配检查 WAF,防御
polyglot JAR 通道只放行图片 上传校验,防御
ASM 非法类名 类名必须是一个 URL 链内构造:攻击者自造的中段需求
/proc/self/fd 匿名临时文件需变为可寻址路径 链内构造:JDK 落盘行为 × 攻击者的路径选择

约束的最大来源是防御本身。防御每上线一次,就在客观上批量催生了一批潜在的配对。checkAutoType 上线那天,"整数 IP 活过点号摧毁"就客观成立了,只是还没有人完成它。第二来源是链内状态:组合链走到中途,会自造中段需求。

3.3 配对:feat + 约束 = trick

trick 诞生的过程可以拆成四步:

  1. 约束出现:防御部署或链内状态制造了新约束
  2. 候选生成:在 feat 库存里找满足约束的候选
  3. 实证验证:跑一次。整数 IP 活过变换了吗?活了
  4. 著录:命名、抽象、按场景索引,进入可复用库存

候选生成的四个来源,机械化的程度不一样:

来源 机制 例子 可机械化程度
feat 注册表读取 读组件的扩展点清单 transport 槽的完整候选 = 运行时注册的 URLStreamHandler 集合 完全
差分挖掘 两个实现互测,找分歧点 desync 研究、parser differential fuzzing 高,但需人工做相关性分诊
社区知识检索 writeup/CTF/漏洞史按场景重索引 整数 IP、polyglot 的跨场景迁移 中,本质是老约束下的配对迁移
中段约束生产 链内自造需求并求解 /proc/self/fd 两阶段 低,真正的发明前沿

这解释了"涌现"到底是怎么回事:一个新约束来了,在既有索引里查不到哪个 feat 满足它,配对只能靠人的识别。涌现不是 trick 的神秘属性,而是约束的新颖性暂时击败了索引。老约束(过滤器、类型检查、出口管控)是个反复出现的小家族,所以老配对能跨场景迁移。这也是社区知识在新约束面前必然失灵的原因。

这也解释了为什么"我想要一个 trick"是个没办法回答的问题。没有约束,就没有成功标准(第 3 步无从验证)、没有检索键(第 2 步无从定向)。约束把许愿变成了可解的问题:它同时给出成功标准、检索方向和配对成立的判定条件。上篇的格子矩阵回头看正是一排等待配对的约束:格子提问,feat 作答。

3.4 人的判断用在了哪

上面说的四步里,有两个子类型当前还依赖人的判断,暂时没有完备的机械方法。

一是链内自造需求。组合链走到中途,产生了"需要不含点的标识符""需要匿名文件可寻址"这样的中段需求——这些需求从哪来、怎么定义,当前没有机械方法。利用链的构造,本质上是把"我要 RCE"逐级分解成一串定义清楚的子目标,每个子目标再去找配对的 feat。fastjson 案就是例子:三个中段约束一旦被分解出来,求解反而便宜;贵的是分解本身。

二是新约束下的候选配对。老约束(过滤器、类型检查、出口管控)是个反复出现的小家族,既有索引能覆盖;新约束第一次出现时,索引里查不到哪个 feat 满足它,只能靠人的启发式搜索。

这两处都没有完备的机械方法。但这不说明"原则上不可自动化"——二进制世界的 gadget 扫描一开始也没有。边界在哪应该实测,不由言辞划定。

4. 能不能自动化

"trick 能不能被完整枚举"需要实测,不是嘴上说说。按上面的层次拆开:

可枚举性 依据
feat 层(单组件行为存量) 可机械化扫描 差分测试、spec 挖掘、注册表读取均有成功先例
约束层 可枚举,但持续新生 从部署中提取约束是机械操作;新约束主要来自防御部署,一直在长
跨系统组合 Gap 退化为环境枚举问题 按行为等价类采样,而非版本枚举
候选配对搜索(无基底处) 启发式搜索,无完备性保证 空间可枚举,但无基底时完备枚举不可行
配对检验(给定 feat + 约束) 机械、便宜 跑一次实验就知道

feat 层里,一部分 feat 枚举可以降级为配置枚举。fastjson 案 transport 槽位的完整候选清单,不在任何 writeup 里,而在目标运行时实际注册的 URL handler 集合里。读注册表是机械操作。行为层缺的对应物,原因要拆成两半:扫描本身没人做,配对验证做得起的人少。feat 扫描廉价(读配置、起一次服务),但候选配对的作用域实测要起环境,单条成本远高于 gadget 的静态校验。

一个可以跑的测量方案:对 Java 资源加载 transport 槽跑一条 feat 扫描流水线。枚举 JDK 内建 handler 和常见容器注册的 handler,逐个实测 jar:<proto>: 资源名行为,对照已知库存(http/https/file/ftp 等)算召回率。收回已知项说明机械扫描有效;漏掉的精确定位问题出在哪个连接层;发现新项直接扩充库存。三类结果都有信息量,实验说了算。

5. 参考资料

5.1 原始披露

来源 URL 日期
Kirill Firsov 首次披露推文(另见同作者推文 2078872296476057713,奇安信通告引用) https://x.com/k_firsov/status/2078872293745570032 2026-07-19
原作者完整研究文档(fearsoff.org) https://fearsoff.org/cn/research/fastjson-1-2-83-rce 2026-07-22

5.2 官方与厂商通告

来源 URL
官方 Security Advisory(alibaba/fastjson2 Wiki) https://github.com/alibaba/fastjson2/wiki/Security-Advisory:-Remote-Code-Execution-in-fastjson-1.2.68%E2%80%931.2.83
CVE-2026-16723(NVD) https://nvd.nist.gov/vuln/detail/CVE-2026-16723
腾讯云早期通告 https://cloud.tencent.com/announce/detail/2375
腾讯云 CVE-2026-16723 正式通告 https://cloud.tencent.com/announce/detail/2381
奇安信 CERT 首次通告(QVD-2026-43021) https://www.secrss.com/articles/92316
奇安信 CERT 复现更新(gm7.org 转载) https://www.gm7.org/archives/132224
fastjson 1.2.83 Release 页 https://github.com/alibaba/fastjson/releases
猎户攻防实验室预警 https://www.gm7.org/archives/131981
大连理工大学安全公告 https://wlaq.dlut.edu.cn/info/1097/8405.htm

5.3 技术分析

来源 URL
9eek 源码级分析(技术栈 / 博客园) https://jishuzhan.net/article/2079870411771879426https://www.cnblogs.com/9eek/p/21793398
LoRexxar 技术分析 https://lorexxar.cn/2026/07/21/fs1-2-83rce/
GCSA / Foresight 深度分析 https://foresightnews.pro/article/detail/98990
Detect-DefenseLab 全版本通杀研究 https://github.com/Detect-DefenseLab/fastjson-1.2.83-rce-detection
FreeBuf 完整分析复现 https://www.freebuf.com/articles/web/491546.html
Mrxn 默认配置 RCE 分析 https://mrxn.net/jswz/fastjson-1-2-83-default-config-rce.html
CSDN PoC 综合分析 https://blog.csdn.net/lingggggaaaa/article/details/163064101
CSDN 实战复现与企业排查 https://blog.csdn.net/weixin_42376192/article/details/163121562

5.4 PoC 仓库

仓库 URL 备注
wouijvziqy/Fastjson-JsonType-RCE-PoC https://github.com/wouijvziqy/Fastjson-JsonType-RCE-PoC 最早公开,已删除
ThanatosXingYu/2026FastjsonPoC https://github.com/ThanatosXingYu/2026FastjsonPoC 完整构建脚本与复现步骤
seklure/fastjson-jsontype-rce-poc https://github.com/seklure/fastjson-jsontype-rce-poc 轻量 PoC
dinosn/fastjson-jsontype-rce-lab https://github.com/dinosn/fastjson-jsontype-rce-lab Docker 靶场
fzypl/Fastjson-JsonType-RCE-Demo https://github.com/fzypl/Fastjson-JsonType-RCE-Demo 演示 Demo
zg-sec/fastjson-json-type-rce https://github.com/zg-sec/fastjson-json-type-rce 诸葛安全版
YoungBear/FastjsonPoc https://github.com/YoungBear/FastjsonPoc 通用 PoC
Detect-DefenseLab/fastjson-1.2.83-rce-detection https://github.com/Detect-DefenseLab/fastjson-1.2.83-rce-detection 全版本通杀 + 三层防御

5.5 工具与文档

搜索文章

输入关键词开始搜索。