本文首發於先知:https://xz.aliyun.com/t/6914
漏洞分析
FastJson1.2.24 RCE
在分析1.2.47的RCE之前先對FastJson1.2.24版本中的RCE進行一個簡單的漏洞鏈分析,為在本篇文章后面1.2.47中漏洞的調用過程做個鋪墊。在本文中的1.2.24的payload只研究針對類jdbcRowSetImpl的,因為針對templatesImpl的payload而言在高版本java中要開啟Feature.SupportNonPublicField才能進行對非共有屬性的反序列化處理,因此是存在一定的限制的。因此針對類jdbcRowSetImpl的payload更具有通用型一些。本中所示代碼及jar包請見附件。
exp.java
Exploit.class
首先在本地用marshalsec起一個ldap服務,用rmi也可以,只需將datasourcename中的服務更改為rmi即可
本文中環境為jdk1.8.0,首先在parse處下一個斷點然后運行exp.java,本來打算正向找到setvalue函數,可是fastjson掃描json字符串的過程及反序列化時處理jdbcrowsetimpl類處處理流程過於繁瑣,中間不知道得跟進多少個F7,很容易讓人沒有耐心繼續調試分析,遂這里直接關注漏洞的核心點,即我們在setDataSourceName處下斷點
即此時將從我們payload中指定的DataSourceName中去加載工廠類
在setautoconnect函數中調用了connect()函數,跟進后此時就能看到熟悉的lookup函數啦,我們知道jndi注入攻擊中從遠程加載惡意工廠類即是我們控制了lookup的入口參數,即控制了遠程工廠類的加載地址,即此處即為關鍵點,F7步入
此時getDataSourceName()的返回值也可以看到即為我們所指定的惡意工廠類地址
然后跟進lookup看看如何調用實例化,這里調用了getURLOrDefaultInitCtx(name).lookup()函數
此時就到了java的命名服務管理的類,此時調用getURLContext函數請求ldap,進一步在其中調用getURLObject來通過從遠程的ldap服務獲取Context對象
最終完成exploit.class類的實例化,也就是工廠類的實例化,熟悉的getObjectInstance(),此時就完成了反序列化,從而觸發exploit里的構造函數
整個漏洞的函數調用棧如下圖所示
以下是一些針對fastjson不同版本的payload,可以看到bypass其實在@type類的前面加上L或者[,這都是因為fastjson在處理域時會將掃描到的這些字符進行去除
FastJson1.2.47 RCE
漏洞影響版本:fastjson<1.2.51
exp.java
Exploit.class與1.2.24相同,在1.2.24以后fastjson默認關閉了反序列化任意類的操作,增加了checkautotype,也就是指定要用來反序列化的類不能夠在一些黑名單中,從而做了一定的限制。此次漏洞利用的核心點是java.lang.class這個java的基礎類,在fastjson 1.2.48以前的版本沒有做該類做任何限制,加上代碼的一些邏輯缺陷,造成黑名單以及autotype的繞過。我們仍然在parse處下斷點,這里正向F7跟進到如下圖所示
在DefaultJSONParser中,對json字符串進行掃描解析,此時解析到key值為a,接下來就對a中的字段進行解析,這里解析到@type以后,進而掃描到typename為java.lang.class,此時將調用parseconfig類的checkautotype函數來檢測要反序列化的類。
接着在checkautotype函數中此時從mappings中或者deserializers.findClass()方法來獲取反序列化對象的對應類
這里實際上在findClass函數中的this.bucktet中去找要反序列化的類,而在bucket變量中包含了大量的基礎類
這里關鍵點在這幾句代碼,找到的類通過getName()函數調用后就能獲得類的類名,然后將該類名與傳入的要反序列化的類名比較,若相等,即找到,則直接返回該類,這里返回值為類類型。
接着回到IedentityHashMap的checkautotype函數中返回java.lang.class類
進一步回到com/alibaba/fastjson/parser/DefaultJSONParser.class中調用了對應的序列化處理類,也就是有了要反序列化的類,此時咋處理這類就是接下來的操作,獲取的類為com.alibaba.fastjson.serializer.MiscCodec
接下來到364行即開始調用MiscCodec的deserialze函數
在其deserialze函數中可以看到其首先取得了我們payload字符串中var對應的值,也就是我們要利用的JdbcRowSetImpl類
接着對我們要反序列化的類進行一個類型判斷,一直到303行,此時類滿足判斷,即java.lang.class為類類型的類,所以直接返回strVal,也就是惡意類
在TypeUtils類中可以看到此時loadclass函數的cache默認為true,這也是個重要的導致bypass的地方
那么這里首先loadclass嘗試在mapping中取加載java.lang.class要引入的類,要是沒有找到的話,此時將通過mapping.put方法將該類放到mapping中,那么不需要autotype就完成了對惡意類的加載
完成了對第一部分的json的字符串的解析已經成功加載了惡意類,因此此時json字符串繼續向后掃描,掃描到b,此時處理b對應的值,當掃描到@type時繼續調用checkautotype函數
那么因為之前我們已經通過java.lang.class的加載類的功能將jdbcrowsetimpl類加載到了mappings中,因此這里getclassfrommapping就能夠返回我們的惡意類
接着又回到Defaultjsonparse類中
接着對於jdbcrowsetimpl類,調用fastjsonDeserialize_1_jdbcrowsetimpl類來對該類進行解析調用,那么接下來因為已經bypass了黑名單和autotype,因此之后rce的流程和1.2.24的一樣了
還是回到熟悉的setAutoCommit函數,具體流程見上面1.2.24的描述,最終能夠觸發calc,此時用mashalsec打ldap一樣是可以的
整個漏洞的函數調用棧如下圖所示
漏洞修復
在1.2.48版本的補丁中,首先黑名單做了更新,其中就包含了java.lang.class這個類,並且MiscCodec中也將傳入的cache參數置為了false,這樣通過payload中a部分的java.lang.class引入JdbcRowSetImpl類,b部分通過mappings獲取JdbcRowSetImpl類的方法就失效了,並且1.2.48以下開不開autotype都能打。
參考
https://www.anquanke.com/post/id/181874
https://www.kingkk.com/2019/07/Fastjson%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-1-2-24-1-2-48/
https://saucer-man.com/information_security/346.html
https://javaweb.org.cn/3240.shtml這個講的很詳細