Fastjson autoType 防御演进:从 checkAutoType 到 safeMode
Fastjson autoType 防御演进从 checkAutoType 到 safeMode写在前面前面几篇都在讲“怎么绕”–1.2.24 裸奔、1.2.41 前后缀、1.2.47 缓存、1.2.80 Throwable。这篇换个视角站在官方这边看 fastjson 从 1.2.25 到 1.2.68 是怎么一步步把 autoType 堵起来的以及为什么最后不得不祭出safeMode。把防御演进串一遍你会发现一条清晰的轨迹黑名单 - 哈希黑名单 - 修缓存 - safeMode。每一步都是被绕过倒逼出来的而每一步的局限也正好对应前面某篇的绕过。防御梳理篇非单个漏洞不设 CVE 行。纯源码无截图。一、1.2.25三道防线1.2.24 裸奔之后1.2.25 一次性上了三道防线默认关 autoTypeParserConfig.autoTypeSupport false。checkAutoType 黑名单loadClass之前插入类型校验。明文前缀黑名单denyListstartsWith。// 1.2.25 parseObject 分支对比 1.2.24 的直接 loadClassClass?clazzconfig.checkAutoType(typeName,null);// ★ 先过校验// 通过才 loadClass这三道防线对应了三个攻击面开关、校验函数、黑名单内容。后面每被绕一次官方就补其中一道。二、1.2.42黑名单哈希化1.2.41 的L前缀绕过根因是startsWith匹配原始串、loadClass 却翻译类名。1.2.42 一边修这个剥L再查一边把明文黑名单改成哈希防逆向// 1.2.42明文 denyList - 哈希 denyHashCodeslonghashTypeUtils.fnv1a_64(className);for(inti0;idenyHashCodes.length;i){if(hashdenyHashCodes[i])throw...;}明文类名不再出现在 jar 里逆向成本上升。但哈希只是换了比对方式本质还是黑名单–枚举坏值防不住未知。1.2.43 的LL双写很快又绕了只剥一层L的修复不彻底。三、1.2.48修缓存投毒1.2.47 的缓存绕过根因是 checkAutoType “先查缓存、后查黑名单”且 MiscCodec 用cachetrue把用户输入的类塞进缓存。1.2.48 从两头堵// ① MiscCodec 不再写缓存cache 改 falseTypeUtils.loadClass(strVal,classLoader,false);// ② checkAutoType 黑名单前置先查黑名单再查缓存if(autoTypeSupport||expectClassnull){longhashfnv1a_64(className);for(inti0;idenyHashCodes.length;i){if(hashdenyHashCodes[i])throw...;// ★ 黑名单前置}}Class?clazzTypeUtils.getClassFromMapping(typeName);// 再查缓存这步把“校验顺序”和“缓存写入”两个根因都修了。但黑名单本身仍在–只要不开 safeMode后续仍可能被新姿势绕1.2.80 就是。四、1.2.68safeMode 终结技绕过一次次打脸后1.2.68 终于上了根本性方案safeMode。它的逻辑简单到粗暴–不再区分好类坏类直接不认type// 1.2.68 checkAutoType 顶部publicClass?checkAutoType(StringtypeName,Class?expectClass){if(safeMode){thrownewJSONException(safeMode not support autoType: typeName);}...}开启方式ParserConfig.getGlobalInstance().setSafeMode(true);safeMode 之所以是“终结技”因为它改变了防御模型模型逻辑局限黑名单1.2.25~1.2.66枚举坏类其余放行新坏类/绕过姿势层出不穷safeMode1.2.68一律不放行type牺牲 autoType 功能但本就该禁黑名单是“默认允许禁止坏值”safeMode 是“默认禁止”。前者永远追着攻击者跑后者一劳永逸–代价是 autoType 功能没了但 autoType 本就是 RCE 温床这个代价值。五、演进时间线1.2.24 autoType 裸奔(无校验) ── CVE-2017-18349 1.2.25 checkAutoType 明文黑名单 关autoType 三道防线 1.2.41 L 前缀绕过 ── 校验/加载不一致 1.2.42 黑名单哈希化 修L(只剥一层) 1.2.43 LL 双写绕过 - 直接拒 L/[ 前缀 1.2.47 缓存绕过(无需autoType) ── 校验顺序漏洞 1.2.48 修缓存(cachefalse 黑名单前置) 1.2.68 ★ safeMode(默认false) ── 终结技,但需手动开 1.2.80 Throwable expectClass 绕过 ── safeMode未开时的旁路 1.2.83 收窄 expectClass一条规律每一版黑名单都被绕每一次绕过都修在“根因”顺序、一致性、旁路直到 safeMode 把根–“允许加载任意类”–整个砍掉。六、为什么 safeMode 是唯一正解回看整条演进黑名单路线失败的根因是它试图在“允许任意类加载”的前提下挑出危险类。但“任意类加载”本身就是 RCE 的全部前提–攻击者只要找到一个你没列进黑名单的危险类或绕过匹配的姿势就赢了。这是个不对称博弈防御要枚举全攻击只要找一个漏。safeMode 之所以赢是它退出了这个博弈–不再尝试分辨好坏直接禁掉 autoType。这印证了安全设计的一条铁律默认安全default deny 黑名单default allow deny list。fastjson 用了从 1.2.25 到 1.2.68、四十多个版本、无数次打脸才走完从“黑名单”到“默认禁止”这条路。而这个教训1.2.24 时代就埋下了–autoType 这个“灵活还原任意对象”的功能从设计上就是危险的后面所有的补丁都是在给这个设计原罪打补丁。七、给使用者的建议开 safeModeParserConfig.getGlobalInstance().setSafeMode(true)或升级到 fastjson2默认安全。别开 autoType业务没强需求就别setAutoTypeSupport(true)开了等于自废武功。升级1.2.83 以上修了 expectClass 旁路fastjson2 重写默认安全。WAF 拦type只能当补充挡不住变种别依赖。参考fastjson GitHubhttps://github.com/alibaba/fastjson系列漏洞篇1.2.24 autoType 分析、1.2.41-43 黑名单绕过、1.2.47 缓存绕过、1.2.80 Throwable 绕过