作者:kejaly@白帽匯安全研究院
校對:r4v3zn@白帽匯安全研究院

前言

在研究高版本 JDK 反序列化漏洞的時候,往往會涉及到 JEP 290 規范。但是網上公開針對 JEP 290 規范原理研究的資料并不是很多,這就導致在研究高版本 java 反序列化的時候有些無能為力,所以最近對 JEP 290 規范好好的研究的一番,輸出這篇文章,希望和大家一起交流學習。

簡介

官方描述:Filter Incoming Serialization Data,即過濾傳入的序列化數據。

image-20210818095109225

主要內容有:

  • Provide a flexible mechanism to narrow the classes that can be deserialized from any class available to an application down to a context-appropriate set of classes.【提供了一個靈活的機制,將可以反序列化的類從應用程序類縮小到適合上下文的類集(也就是說提供一個限制反序列化的類的機制,黑白名單方式)。】
  • Provide metrics to the filter for graph size and complexity during deserialization to validate normal graph behaviors.(限制反序列化深度和復雜度)
  • Provide a mechanism for RMI-exported objects to validate the classes expected in invocations.【為 RMI 導出的對象設置了驗證機制。( 比如對于 RegistryImpl , DGCImpl 類內置了默認的白名單過濾)】
  • The filter mechanism must not require subclassing or modification to existing subclasses of ObjectInputStream.
  • Define a global filter that can be configured by properties or a configuration file.(提供一個全局過濾器,可以從屬性或者配置文件中配置)

JEP 290 在 JDK 9 中加入,但在 JDK 6,7,8 一些高版本中也添加了:

Java? SE Development Kit 8, Update 121 (JDK 8u121)

Java? SE Development Kit 7, Update 131 (JDK 7u131)

Java? SE Development Kit 6, Update 141 (JDK 6u141)

官方文檔:https://openjdk.java.net/jeps/290

JEP 290 核心類

JEP 290 涉及的核心類有: ObjectInputStream 類,ObjectInputFilter 接口,Config 靜態類以及 Global 靜態類。其中 Config 類是 ObjectInputFilter接口的內部類,Global 類又是Config類的內部類。

ObjectInputStream 類

JEP 290 進行過濾的具體實現方法是在 ObjectInputStream 類中增加了一個serialFilter屬性和一個 filterChcek 函數,兩者搭配來實現過濾的。

構造函數

有兩個構造函數,我們需要關注的是在這兩個構造函數中都會賦值 serialFilter 字段為 ObjectInputFilter.Config.getSerialFilter():

image-20210827185257711

image-20210827185310172

ObjectInputFilter.Config.getSerialFilter() 返回 ObjectInputFilter#Config 靜態類中的 serialFilter靜態字段

image-20210827185402971

image-20210827185418278

serialFilter 屬性

image-20210818104237263

serialFilter 屬性是一個 ObjectInputFilter 接口類型,這個接口聲明了一個 checkInput 方法(關于 ObjectInputFilter 后面會更細致的講解)。

image-20210818104509197

filterCheck 函數

image-20210818104147822

filterCheck 函數邏輯可以分三步。

第一步,先會判斷 serialFilter 屬性值是否為空,只有不為空,才會進行后續的過濾操作。

第二步,將我們需要檢查的 class ,以及 arryLength等信息封裝成一個FilterValues對象,

image-20210818105043290

傳入到 serialFilter.checkInput 方法中,返回值為 ObjectInputFilter.Status 類型。

image-20210818105252696

最后一步,判斷 status 的值,如果 statusnull 或者是 REJECTED 就會拋出異常。

ObjectInputStream 總結

到這里可以知道,serialFilter 屬性就可以認為是 JEP 290 中的"過濾器"。過濾的具體邏輯寫到 serialFiltercheckInput 方法中,配置過濾器其實就是設置 ObjectInputStream 對象的 serialFilter屬性。并且在 ObjectInputStream 構造函數中會賦值 serialFilterObjectInputFilter#Config 靜態類的 serialFilter 靜態字段。

ObjectInputFilter 接口

JEP 290 中實現過濾的一個最基礎的接口,想理解 JEP 290 ,必須要了解這個接口。

在低于 JDK 9 的時候的全限定名是 sun.misc.ObjectInputFIlterJDK 9 及以上是 java.io.ObjectInputFilter

另外低于 JDK 9 的時候,是 getInternalObjectInputFiltersetInternalObjectInputFilterJDK 9 以及以上是 getObjectInputFiltersetObjectInputFIlter

先來看一下 ObjectInputFilter接口的結構:

image-20210818101549350

有一個 checkInput 函數,一個靜態類 Config ,一個 FilterInfo 接口,一個 Status 枚舉類。

函數式接口

@FunctionalInterface 注解表明, ObjectInputFilter 是一個函數式接口。對于不了解函數式接口的同學,可以參考:https://www.runoob.com/java/java8-functional-interfaces.html 以及 https://www.jianshu.com/p/40f833bf2c48https://juejin.cn/post/6844903892166148110

在這里我們其實只需要關心函數式接口怎么賦值,函數式接口的賦值可以是: lambda 表達式或者是方法引用,當然也可以賦值一個實現了這個接口的對象。

lambda 賦值:

image-20210809172607772

使用函數引用賦值,比如 RMI 中 RegistryImpl 使用的就是函數引用賦值:

image-20210809172831645

image-20210809172815971

Config 靜態類

Config 靜態類是 ObjcectInputFilter 接口的一個內部靜態類。

image-20210818120140858

Config#configuredFilter 靜態字段

configuredFilter 是一個靜態字段,所以調用 Config 類的時候就會觸發 configuredFilter 字段的賦值。

image-20210818120619722

可以看到會拿到 jdk.serailFilter 屬性值,如果不為空,會返回 createFilter(var0)的結果(createFilter 實際返回的是一個 Global 對象)。

jdk.serailFilter 屬性值獲取的方法用兩種,第一種是獲取 JVM 的 jdk.serialFilter 屬性,第二種通過在 %JAVA_HOME%\conf\security\java.security 文件中指定 jdk.serialFilter 來設置。另外從代碼中可以看到,優先選擇第一種。

Config#createFilter 方法

image-20210818121136424

Config#createFilter 則會進一步調用 Global.createFilter方法,這個方法在介紹 Global 類的時候會說,其實就是將傳入的 JEP 290 規則字符串解析到Global對象的 filters 字段上,并且返回這個 Global 對象。

Config 類的靜態塊

image-20210818121359021

Config 類的靜態塊,會賦值 Config.configuredFilterConfig.serialFilter 上。

Config#getSerialFilter 方法

image-20210818121459319

返回 Config#serialFilter字段值。

Config 靜態類總結

Config 靜態類在初始化的時候,會將Config.serialFilter 賦值為一個Global對象,這個Global 對象的filters字段值是jdk.serailFilter屬性對應的 Function 列表。(關于 Global 對象介紹下面會說到,大家先有這么一個概念)

image-20210818144020117

ObjectInputStream 的構造函數中,正好取的就是 Config.serialFilter 這個靜態字段 , 所以設置了 Config.serialFilter 這個靜態字段,就相當于設置了 ObjectInputStream 類全局過濾器

image-20210810115732377

比如可以通過配置 JVM 的 jdk.serialFilter 或者 %JAVA_HOME%\conf\security\java.security 文件的 jdk.serialFilter 字段值,來設置 Config.serialFilter ,也就是設置了全局過濾。

另外還有就是一些框架,在開始的時候設置也會設置 Config.serialFilter ,來設置 ObjectInputStream 類的全局過濾。 weblogic 就是,在啟動的時候會設置 Config.serialFilterWebLogicObjectInputFilterWrapper 對象。

Global 靜態類

Global 靜態類是 Config 類中的一個內部靜態類。

Global 類的一個重要特征是實現了 `ObjectInputFilter 接口,實現了其中的 checkInput 方法。所以 Global 類可以直接賦值到 ObjectInputStream.serialFilter 上。

image-20210818121707155

Global#filters 字段

是一個函數列表。

image-20210812113720175

Global#checkInput 方法

Global 類的 checkInput 會遍歷 filters 去檢測要反序列化的類。

image-20210818121900216

Global 中的構造函數

Global 中的構造函數會解析 JEP 290 規則。Global 中的構造函數的作用用一句話總結就是:解析 JEP 290 規則為對應的 lambda 表達式,然后添加到 Global.filters

JEP 290 的規則如下:

image-20210818122154782

Global 類的構造函數:

image-20210818122231692

image-20210818122336558

image-20210818122401230

具體就是通過 filters add 添加 lambdd 表達式到 filters 中,也就是說對 Globalfilters 賦值的是一個個 lambada 函數。

Global#createFilter 方法

傳入規則字符串,來實例化一個 Global 對象。

image-20210802161719328

Global 類的總結

Global 實現了ObjectInputFilter接口,所以是可以直接賦值到 ObjectInputStream.serialFilter 上。

Global#filters 字段是一個函數列表。

Global 類中的 chekInput 方法會遍歷 Global#filters 的函數,傳入需要檢查的 FilterValues進行檢查(FilterValues 中包含了要檢查的 class, arrayLength,以及 depth 等)。

過濾器

在上面總結 ObjectInputStream 類的中說過,配置過濾器其實就是設置 ObjectInputStream 類中的 serialFilter 屬性。

過濾器的類型有兩種,第一種是通過配置文件或者 JVM 屬性來配置的全局過濾器,第二種則是來通過改變 ObjectInputStreamserialFilter 屬性來配置的局部過濾器。

全局過濾器

設置全局過濾器,其實就是設置Config靜態類的 serialFilter 靜態字段值。

具體原因是因為在 ObjectInputStream 的兩個構造函數中,都會為 serialFilter 屬性賦值為 ObjectInputFilter.Config.getSerialFilter()

image-20210818105907215

image-20210818105854468

ObjectInputFilter.Config.getSerialFilter 就是直接返回 Config#serialFilter

image-20210818120808350

jdk.serailFilter

在介紹 Config 靜態類的時候說到,Config 靜態類初始化的時候,會解析 jdk.serailFilter 屬性設置的 JEP 290 規則到一個 Global 對象的 filters 屬性,并且會將這個 Global 對象賦值到 Config 靜態類的 serialFilter 屬性上。

image-20210818120906421

所以,這里 Config.serialFilter 值默認是解析 jdk.serailFilter 屬性得到得到的 Global 對象。

weblogic 全局過濾器

在 weblogic 啟動的時候,會賦值 Config.serialFilterWebLogicObjectInputFilterWrapper

具體流程如下:

首先在 weblogic 啟動的時候,先調用WeblogicObjectInputFilter.initializeInternal 方法,在 initializeInternal 方法中會先 new一個 JreFilterApiProxy 對象,這個對象是一個進行有關 JEP 290 操作的代理對象(具體原理是通過反射來調用的)。

image-20210819143057398

隨后 new 一個 WeblogicFilterConfig 對象。

image-20210818171333160

在創建 WeblogicFilterConfig 對象的時候中會對 weblogic 黑名單進行整合,最后得到 WeblogicFilterConfigserailFiltergolbalSerailFilter,以及 unauthenticatedSerialFilter屬性如下:

image-20210810145257651

接著調用 filterConfig.getWebLogicSerialFilter取出上面賦值的WeblogicFilterConfig#serailFilter,并調用 filterApliProxy.createFilterForString 方法把filter 字符串轉化為 Object 類型,并且封裝到 WebLogicObjectInputFilterWrapper 對象中。

image-20210810153702790

image-20210819143711589

image-20210819144002411

最后會取出剛剛設置的 filter,傳入 filterApiProxy.setGlobalFilter方法中對 ConfigserialFilter 屬性賦值:

image-20210819144145085

image-20210819144320736

調用完之后我們利用 filterApiProxy.methodConfigGetSerialFilter.invoke(null) 來查看 ConfigserailFilter 字段值, 可以看到 Config.serialFilter 成功被設置為一個 WeblogicObjectInputFilterWrapper 對象。

image-20210819145158768

查看 pattern 正是打了 7 月份補丁的全局反序列化黑名單:

image-20210819145336657

用一段話來闡述 weblogic 中 全局過濾器賦值的流程就是:

weblogic 啟動的時候,會調用 WeblogicObjectInputFilterinitializeInternal 方法進行初始化,首先會new JreFilterApiProxy 對象,這個對象相當于JEP 290 有關操作的代理對象,里面封裝了操作 Config 靜態類的方法。然后會 new 一個 WeblogicFilterConfig 對象,這個對象在 new 的時候會把 weblogic 的黑名單賦值到 WeblogicFilterConfig 對象的屬性中。之后,會從WeblogicFilterConfig 對象屬性中取 serialFilter ,調用 JreFilterApiProxy 對象的 setGlobalFilter 來賦值 Config.serailFilter

局部過濾器

設置局部過濾器的意思是在 new objectInputStream 對象之后,再通過改變單個 ObjectInputStream 對象的 serialFilter字段值來實現局部過濾。

改變單個 ObjectInputStream 對象的 serialFilter 字段是有兩種方法:

1.通過調用 ObjectInputStream 對象的 setInternalObjectInputFilter 方法:

image-20210818181731118

注:低于 JDK 9 的時候,是 getInternalObjectInputFiltersetInternalObjectInputFilterJDK 9 以及以上是 getObjectInputFiltersetObjectInputFIlter

2.通過調用 Config.setObjectInputFilter

image-20210818183207167

局部過濾器典型的例子是 RMI 中針對 RegsitryImplDGCImpl有關的過濾。

RMI 中采用了局部過濾

RMI 簡單介紹

RMI 分為客戶端和服務端,官方文檔:https://docs.oracle.com/javase/tutorial/rmi/overview.html

下面是對 RMI 官方文檔介紹的理解:

另外 RMI 中其實并不一定要 RegistryImpl ,也就是我們熟稱的注冊中心,RMI 完全可以脫離注冊中心來運行。可以參考:https://www.jianshu.com/p/2c78554a3f36 。個人覺得之所以使用注冊中心是因為注冊中心的 Registry_Stub 以及 Registry_Skel 會為我們自動進行底層的協議數據通信(JRMP 協議),能讓使用者可以不關心底層的協議數據交流,而專注在遠程對象的調用上。

RMI 服務端遠程對象導出實際上是將這個對象分裝成一個 Target 對象,然后存放在 ObjectTable#objTable 這個靜態的 HashMap 中:

image-20210827195948085

每個Target對象都包含一個唯一的 id 用來表示一個對象,像 RegistryImplid就比較特殊是 0 ,其他普通對象的 id 都是隨機的:

image-20210827200211131

客戶端要對服務端對象進行遠程調用的時候,是通過這個 id 來定位的。

ObjectTable#putTarget 方法:

image-20210827200428621

ObjectTable#getTarget 方法:

image-20210827200436621

ObjectEndpoint 中的 equals 方法,可以看到是判斷 idtransporttransport 一般情況是相等的,所以一般都是通過 id 來判斷:

image-20210827200447422

RegistryImpl 對象與 JEP 290

RegistryImpl 作為一個特殊的對象,導出在 RMI 服務端,客戶端調用的 bind , lookuplist 等操作,實際上是操作 RegistryImplbindings 這個 Hashtable

image-20210827093525696

bind

image-20210827093630437

lookup

image-20210827093746988

list

image-20210827093853132

這里我們之所以稱RegistryImpl 是一個特殊的對象,是因為 `RegistryImpl 導出過程中生成 Target 對象是一個“定制”的 Target 對象,具體體現在:

1.這個Target 中 id 的 objNum 是固定的,為 ObjID.REGISTRY_ID ,也就是 0 。
2.這個Target 中 disp 是 filter 為 RegisryImpl::RegistryFilter ,skel 為 RegsitryImpl_skel 的 UnicastServerRef 對象。
3.這個Target 中 stub 為 RegistryImpl_stub。

image-20210827110840724

對比普通對象導出過程中生成的 Target

image-20210827111018853

導出過程

首先 LocateRegistry.createRegsitry

image-20210823030959379

image-20210823031216079

new RegistryImpl(port)中會 new 一個UnicastServerRef對象,將 RegistryImplidOBJID.REGISTRY_ID,也就是 0 ) 存入到 LiveRef 對象,隨后 LiveRef對象賦值到 UnicastServerRef 對象中的 ref 字段,并且將 RegsitryImpl::registryFilter 賦值給這個 UnicastServerRef 對象的 filter 字段:

image-20210823031100532

RegistryImplid 是 0 :

image-20210827105704572

隨后在 RegistryImpl#setup 中調用 UnicastServerRef.exportObject 進行對象導出:

image-20210823031307758

UnicastServerRef.exportObject 中會將遠程對象分裝成一個 Target 對象,并且在創建這個 Target 對象的時候,將上面的 UnicastServerRef 對象賦值為 Target中的 disp。于是這個 Target 對象的 disp 就設置為了有 filterUnicastserverRef

image-20210827105122982

隨后調用 LiveRef.exportObject

image-20210827104225262

會調用 TCPEndpoint.export:

image-20210827104252738

調用 TCPTransport.exportObject,在這一步會開啟端口進行監聽:

image-20210827104408036

隨后后調用到 Transport.export,可以看到就是將這個 Target 放到 ObjectTable#objTable 中:

image-20210827104601247

image-20210827104859948

服務端處理請求過程

處理請求是在 Transport#serviceCall,首先從輸入流中讀取 id , 匹配到 RegistryImpl 對象對應的 Target

image-20210823035056151

隨后調用 UnicastServerRef.dispatch

image-20210823035329060

UnicastServerRef#dispatch 中,由于 UnicastServerRef.skel 不為 null ,所以會調用 UnicastServerRef#oldDispatch 方法:

image-20210823035439867

oldDispatch 中會先調用 unmarshalCustomCallData(in) 方法,再調用 RegistryImpl_skel.dispatch 方法。

image-20210823035730050

unmarshalCustomCallData 方法中會進行判斷,如果 UnicastServerRef.filter 不為 null ,就會設置 ConnectionInputStreamserialFilter 字段值為 UnicastServerRef.filter (設置單個 ObjectInputStreamserialFilter 屬性,局部過濾的體現):

image-20210823040109705

再看 RegistryImpl_skel.dispatch

image-20210827112153950

我們以 bind 為例來講解:

image-20210827113103382

DGCImpl 對象與 JEP 290

DGCImpl 對象和 RegistryImpl 對象類似都是一個特殊的對象,他的”定制“ Target 對象的特殊體現在:

1.這個Target 中 id 的 objNum 是固定的,為 ObjID.DGC_ID ,也就是 2 。
2.這個Target 中 disp 是 filter 為 DGCImpl::DGCFilter ,skel 為 DGCImpl_skel 的 UnicastServerRef 對象。
3.這個Target 中 stub 為 DGC_stub。
導出過程

DGCImpl 會在導出 RegsitryImpl 的時候導出,具體分析如下:

DGCImpl靜態代碼塊中會將一個 DGCImpl 封裝為一個 Target 放到 ObjectTable 中,這個 Target 有以下特征:

image-20210825150610369

DGCImpl靜態代碼塊會在 createRegistry 的時候觸發,調用鏈如下:

image-20210825150936069

具體原因是在導出 RegistryImpl 對象的時候,會傳入 permanenttrue :

image-20210825151132318

image-20210825151146319

就會導致 new Target 中會觸發 pinImpl 方法:

image-20210825151220963

image-20210825151228499

然后在調用 WeakRef.pin 方法的時候,會觸發 DGCImpl 的靜態代碼塊。

image-20210825151048159

也就是說在 createRegistry 的時候,會把 DGCImplRegistryImpl 封裝的 target 都放到 ObjectTable#objTable 中。

image-20210825151704113

服務端處理請求過程

服務端處理 DGCImpl的請求過程和 RegistryImpl 非常類似,都是在Transport#serviceCall中處理,調用 UnicastServerRef#dispatch,再調用UnicastServerRef#oldDispatch 最后在 UnicastServerRef#unmarshalCustomCallData 中為之后進行readObject 操作的 ConnectionInputStream.serialFilter 賦值為 DGCImpl::checkInput

DGCImpl#checkInput

image-20210827120113771

通過 JVM 參數或者配置文件進行配置

對于 RegistryImpl

RegistryImpl 中含有一個靜態字段 registryFilter ,所以在 new RegistryImpl對象的時候,會調用 initRegistryFilter 方法進行賦值:

image-20210827120702592

initRegistryFilter方法會先讀取 JVM 的 sun.rmi.registry.registryFilter 的屬性,或者是讀取 %JAVA_HOME%\conf\security\java.security 配置文件中的 sun.rmi.registry.registryFilter 字段來得到 JEP 290 形式的 pattern ,再調用 ObjectInputFilter.Config.createFilter2 創建 filter并且返回。

image-20210827120710219

image-20210827121013640

%JAVA_HOME\conf\security\java.security% 文件:

image-20210827121307116

RegistryImpl#registryFilter函數會先判斷 RegistryImpl#regstiryFilter 字段是否為 null 來決定使用用戶自定義的過濾規則,還是使用默認的白名單規則,如果不是 null 的話,會先調用用戶自定義的過濾規則進行檢查,接著判斷檢查結果,如果不是 UNDECIDED 就直接返回檢查的結果,否則再使用默認的白名單檢查。

image-20210827120629723

對于 DGCImpl

DGCImpl 中含有一個靜態字段 dgcFilter ,所以在 new DGCImpl對象的時候,會調用 initDgcFilter 方法進行賦值:

image-20210827123958702

image-20210827124038685

initDgcFilter方法會先讀取 JVM 的 sun.rmi.transport.dgcFilter 的屬性,或者是讀取 %JAVA_HOME\conf\security\java.security% 配置文件中的 sun.rmi.transport.dgcFilter 字段來得到 JEP 290 形式的 pattern ,再調用 ObjectInputFilter.Config.createFilter 創建 filter并且返回。

%JAVA_HOME%\conf\security\java.security 文件:

image-20210827124354081

DGCImpl#checkInputRegistryImpl#registryFilter函數類似,會先判斷 DGCImpl#dgcFilter 字段是否為 null 來決定使用用戶自定義的過濾規則,還是使用默認的白名單規則,如果不是 null 的話,會先調用用戶自定義的過濾規則進行檢查,接著判斷檢查結果,如果不是 UNDECIDED 就直接返回檢查的結果,否則再使用默認的白名單檢查。

RMI 中 JEP 290 的繞過

網上公開資料廣泛說的是:如果服務端"綁定"了一個對象,他的方法參數類型是Object 類型的方法時,則可以繞過 JEP 290。

其實剖析本質,是因為服務端導出的這個 ”普通的對象“ 對應的 Target 對象中的 disp (其實是 UnicastServerRef 對象) 的 filternull

image-20210827153542315

普通的對象導出的 target 如下:

image-20210827111018853

下面我們來具體跟以下流程分析,首先準備客戶端和服務端代碼如下:

服務端和客戶端共同包含接口的定義和實現:

image-20210827162757132

image-20210827162853386

服務端代碼如下:

image-20210827162918895

惡意客戶端代碼如下:

image-20210827162644801

普通對象的導出過程

普通對象的導出有兩種方式,一種是繼承 UnicastRemoteObject 對象,會在 new 這個對象的時候自動導出。第二種是如果沒有繼承 UnicastRemoteObject 對象,則需要調用UnicastRemoteObject.export進行手動導出。但其實第一種底層也是利用 UnicastRemoteObject.export 來導出對象的。

下面我們來討論繼承 UnicastRemoteObject 類的情況:

image-20210827154244742

image-20210827154647314

因為這個普通對象繼承自 UnicastRemoteObject類,所以在 new 這個普通對象的時候會調用到 UnicastRemoteObject 的構造方法:

image-20210823005130988

進而調用 UnicastRemoteObject.exportObject 方法:

image-20210827155229168

image-20210827155144661

UnicastRemoteObject#exportObject 方法中再使用 UnicastServerRef#exportObject ,這里可以看到在 new UnicastRemoteObject 的時候并沒有傳入 filter

image-20210823005152153

對比導出 RegistryImpl 對象的時候, new UnicastRemoteObject 對象傳入了 RegistryImpl::registryFilter

image-20210823031100532

接著會調用 UnicastServerRef.exportObject 方法:

image-20210823005405191

所以普通對象生成的 Target 對象的 disp 中 filter 就為 null ,另外這里的 skel 也為 null 。

image-20210823005742261

后面導出 Target 的過程和 導出RegistryImpl對應的 Target是一樣的,最后會將這個普通對象的 Target 放到 objectTable#objTable中。

綁定成功后的 ObjectTable#objTable:

image-20210827163249786

服務端處理請求的過程

同樣處理請求的入口在 Transport#serviceCall,首先從輸入流中讀取 id , 匹配到 RegistryImpl 對象對應的 Target

image-20210827162445281

然后取出 disp ,調用 disp.dispatch

image-20210827163553250

首先由于 skelnull ,所以不會進入 oldDispatch , 像 RegistryImplDGCImpl 因為他們的 skel 不為 null ,所以會進入到 oldDispatch

image-20210827163735583

接著會匹配到方法,拿到方法的參數,接著進行反序列化:

image-20210827164817344

unmarshalCustomCallData 方法:

image-20210827164857411

unmarshalValue 方法對輸入流中傳入的參數進行反序列化:

image-20210827165045896

執行 in.readObject 之后,成功彈出計算器:

image-20210827165135739

反制

利用上面這種方法繞過 JEP 290 去攻擊 RMI 服務端,網上有一些工具,比如 rmitast 和 rmisout 。

但是對于使用 rmitast 或者 rmisout 這些工具,或者調用 lookup() 來試圖攻擊RMI 服務端 的時候,我們可以使用 如下的惡意服務端代碼進行反制:

image-20210827165940844

反制 RegistryImpl_Stub.lookup

我們來看一下RegistryImpl_Stub.lookup對服務端返回的結果是怎么處理的,可以看見在 RegistryImpl_Stub.lookup 會直接對服務端返回的對象調用 in.readObject 方法,而 inserialFilter在這里是為 null 的:

image-20210827171556540

所以客戶端在進行 RegistryStub.lookup 操作的時候會直接導致 RCE :

image-20210827173057156

同理 RegistryStub.list 也是如此:

image-20210827172117128

但是用上面的服務端惡意代碼并不能觸發 RCE ,因為上面服務端惡意代碼是利用 Registry_skel 來寫入對象的,可以看到寫入的是一個字符串數組:

image-20210827172909054

反制 rmitast

我們以 rmitast 中的 枚舉模塊為例:

image-20210827173427559

步入 enumerate.enumerate() 里面是具體的實現原理:

image-20210827173510006

首先 Enumerate.connect(this.registry) 返回的實際上是 RegistryImpl_Stub 對象,底層調用的是 LocateRegistry.getRegistry 方法。

然后調用 this.registry.loadObjects(), this.list() 實際調用的是 RegistyImpl_Stub.list() 方法,得到注冊中心的所有綁定的對象名:

image-20210827173748613

接著會調用 this.loadObjects(names), 會調用 this.lookup(name) ,底層實際使用的是 RegistryImpl_Stub.lookup() 方法,上面分析過 RegistryImpl_Stub.lookup 會直接反序列化服務端傳過來的惡意對象,并且 readObject時使用的ObjectInputStream 對象中的 serialFilternull

image-20210827173943082

我們啟動上面的惡意服務端,然后使用 RmiTaste 的 enum 模塊:

image-20210827174102324

運行之后會導致使用 RmiTast 的一端 RCE :

image-20210827174304598

總結

JEP 290 主要是在 ObjectInputStream 類中增加了一個serialFilter屬性和一個 filterChcek 函數,其中 serialFilter就可以理解為過濾器。

ObjectInputStream 對象進行 readObject 的時候,內部會調用 filterChcek 方法進行檢查,filterCheck方法中會對 `serialFilter屬性進行判斷,如果不是 null ,就會調用 serialFilter.checkInput 方法進行過濾。

設置過濾器本質就是設置 ObjectInputStreamserialFilter 字段值,設置過濾器可以分為設置全局過濾器和設置局部過濾器:

1.設置全局過濾器是指,通過修改 Config.serialFilter這個靜態字段的值來達到設置所有 ObjectInputStream對象的 serialFilter值 。具體原因是因為 ObjectInputStream 的構造函數會讀取Config.serialFilter的值賦值到自己的serialFilter字段上,所有就會導致所有 new 出來的 ObjectInputStream對象的 serailFilter 都為Config.serialFilter的值。

2.設置局部過濾器是指,在 new ObjectInputStream 的之后,再修改單個 ObjectInputStream 對象的 serialFilter 字段值。

參考

https://www.cnpanda.net/sec/968.html JEP290的基本概念

https://www.jianshu.com/p/2c78554a3f36 深入理解rmi原理

http://openjdk.java.net/jeps/290 JEP 290 官方文檔

https://www.cnblogs.com/Ming-Yi/p/13832639.html Java序列化過濾器

https://www.runoob.com/java/java8-functional-interfaces.html Java 8 函數式接口

https://www.jianshu.com/p/40f833bf2c48 函數式接口和Lambda表達式深入理解

https://juejin.cn/post/6844903892166148110 「Java8系列」神奇的函數式接口


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1689/