:從 checkAutoType 到 safeMode)
Fastjson autoType 防御演進(jìn)從 checkAutoType 到 safeMode寫在前面前面幾篇都在講“怎么繞”–1.2.24 裸奔、1.2.41 前后綴、1.2.47 緩存、1.2.80 Throwable。這篇換個(gè)視角站在官方這邊看 fastjson 從 1.2.25 到 1.2.68 是怎么一步步把 autoType 堵起來的以及為什么最后不得不祭出safeMode。把防御演進(jìn)串一遍你會(huì)發(fā)現(xiàn)一條清晰的軌跡黑名單 - 哈希黑名單 - 修緩存 - safeMode。每一步都是被繞過倒逼出來的而每一步的局限也正好對(duì)應(yīng)前面某篇的繞過。防御梳理篇非單個(gè)漏洞不設(shè) CVE 行。純?cè)创a無截圖。一、1.2.25三道防線1.2.24 裸奔之后1.2.25 一次性上了三道防線默認(rèn)關(guān) autoTypeParserConfig.autoTypeSupport false。checkAutoType 黑名單loadClass之前插入類型校驗(yàn)。明文前綴黑名單denyListstartsWith。// 1.2.25 parseObject 分支對(duì)比 1.2.24 的直接 loadClassClass?clazzconfig.checkAutoType(typeName,null);// ★ 先過校驗(yàn)// 通過才 loadClass這三道防線對(duì)應(yīng)了三個(gè)攻擊面開關(guān)、校驗(yàn)函數(shù)、黑名單內(nèi)容。后面每被繞一次官方就補(bǔ)其中一道。二、1.2.42黑名單哈希化1.2.41 的L前綴繞過根因是startsWith匹配原始串、loadClass 卻翻譯類名。1.2.42 一邊修這個(gè)剝L再查一邊把明文黑名單改成哈希防逆向// 1.2.42明文 denyList - 哈希 denyHashCodeslonghashTypeUtils.fnv1a_64(className);for(inti0;idenyHashCodes.length;i){if(hashdenyHashCodes[i])throw...;}明文類名不再出現(xiàn)在 jar 里逆向成本上升。但哈希只是換了比對(duì)方式本質(zhì)還是黑名單–枚舉壞值防不住未知。1.2.43 的LL雙寫很快又繞了只剝一層L的修復(fù)不徹底。三、1.2.48修緩存投毒1.2.47 的緩存繞過根因是 checkAutoType “先查緩存、后查黑名單”且 MiscCodec 用cachetrue把用戶輸入的類塞進(jìn)緩存。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);// 再查緩存這步把“校驗(yàn)順序”和“緩存寫入”兩個(gè)根因都修了。但黑名單本身仍在–只要不開 safeMode后續(xù)仍可能被新姿勢(shì)繞1.2.80 就是。四、1.2.68safeMode 終結(jié)技繞過一次次打臉后1.2.68 終于上了根本性方案safeMode。它的邏輯簡(jiǎn)單到粗暴–不再區(qū)分好類壞類直接不認(rèn)type// 1.2.68 checkAutoType 頂部publicClass?checkAutoType(StringtypeName,Class?expectClass){if(safeMode){thrownewJSONException(safeMode not support autoType: typeName);}...}開啟方式ParserConfig.getGlobalInstance().setSafeMode(true);safeMode 之所以是“終結(jié)技”因?yàn)樗淖兞朔烙P湍P瓦壿嬀窒藓诿麊?.2.25~1.2.66枚舉壞類其余放行新壞類/繞過姿勢(shì)層出不窮safeMode1.2.68一律不放行type犧牲 autoType 功能但本就該禁黑名單是“默認(rèn)允許禁止壞值”safeMode 是“默認(rèn)禁止”。前者永遠(yuǎn)追著攻擊者跑后者一勞永逸–代價(jià)是 autoType 功能沒了但 autoType 本就是 RCE 溫床這個(gè)代價(jià)值。五、演進(jìn)時(shí)間線1.2.24 autoType 裸奔(無校驗(yàn)) ── CVE-2017-18349 1.2.25 checkAutoType 明文黑名單 關(guān)autoType 三道防線 1.2.41 L 前綴繞過 ── 校驗(yàn)/加載不一致 1.2.42 黑名單哈希化 修L(只剝一層) 1.2.43 LL 雙寫繞過 - 直接拒 L/[ 前綴 1.2.47 緩存繞過(無需autoType) ── 校驗(yàn)順序漏洞 1.2.48 修緩存(cachefalse 黑名單前置) 1.2.68 ★ safeMode(默認(rèn)false) ── 終結(jié)技,但需手動(dòng)開 1.2.80 Throwable expectClass 繞過 ── safeMode未開時(shí)的旁路 1.2.83 收窄 expectClass一條規(guī)律每一版黑名單都被繞每一次繞過都修在“根因”順序、一致性、旁路直到 safeMode 把根–“允許加載任意類”–整個(gè)砍掉。六、為什么 safeMode 是唯一正解回看整條演進(jìn)黑名單路線失敗的根因是它試圖在“允許任意類加載”的前提下挑出危險(xiǎn)類。但“任意類加載”本身就是 RCE 的全部前提–攻擊者只要找到一個(gè)你沒列進(jìn)黑名單的危險(xiǎn)類或繞過匹配的姿勢(shì)就贏了。這是個(gè)不對(duì)稱博弈防御要枚舉全攻擊只要找一個(gè)漏。safeMode 之所以贏是它退出了這個(gè)博弈–不再嘗試分辨好壞直接禁掉 autoType。這印證了安全設(shè)計(jì)的一條鐵律默認(rèn)安全default deny 黑名單default allow deny list。fastjson 用了從 1.2.25 到 1.2.68、四十多個(gè)版本、無數(shù)次打臉才走完從“黑名單”到“默認(rèn)禁止”這條路。而這個(gè)教訓(xùn)1.2.24 時(shí)代就埋下了–autoType 這個(gè)“靈活還原任意對(duì)象”的功能從設(shè)計(jì)上就是危險(xiǎn)的后面所有的補(bǔ)丁都是在給這個(gè)設(shè)計(jì)原罪打補(bǔ)丁。七、給使用者的建議開 safeModeParserConfig.getGlobalInstance().setSafeMode(true)或升級(jí)到 fastjson2默認(rèn)安全。別開 autoType業(yè)務(wù)沒強(qiáng)需求就別setAutoTypeSupport(true)開了等于自廢武功。升級(jí)1.2.83 以上修了 expectClass 旁路fastjson2 重寫默認(rèn)安全。WAF 攔type只能當(dāng)補(bǔ)充擋不住變種別依賴。參考fastjson GitHubhttps://github.com/alibaba/fastjson系列漏洞篇1.2.24 autoType 分析、1.2.41-43 黑名單繞過、1.2.47 緩存繞過、1.2.80 Throwable 繞過