作者: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 的值,如果 status 是 null 或者是 REJECTED 就會拋出異常。

ObjectInputStream 總結

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

ObjectInputFilter 接口

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

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

另外低于 JDK 9 的時候,是 getInternalObjectInputFilter 和 setInternalObjectInputFilter,JDK 9 以及以上是 getObjectInputFilter 和 setObjectInputFIlter 。

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

image-20210818101549350

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

函數式接口

@FunctionalInterface 注解表明, ObjectInputFilter 是一個函數式接口。對于不了解函數式接口的同學,可以參考:https://www.runoob.com/java/java8-functional-interfaces.html 以及 https://www.jianshu.com/p/40f833bf2c48 , https://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.configuredFilter 到 Config.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.serialFilter 為 WebLogicObjectInputFilterWrapper 對象。

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 中,也就是說對 Global 的 filters 賦值的是一個個 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 屬性來配置的全局過濾器,第二種則是來通過改變 ObjectInputStream 的 serialFilter 屬性來配置的局部過濾器。

全局過濾器

設置全局過濾器,其實就是設置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.serialFilter 為 WebLogicObjectInputFilterWrapper 。

具體流程如下:

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

image-20210819143057398

隨后 new 一個 WeblogicFilterConfig 對象。

image-20210818171333160

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

image-20210810145257651

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

image-20210810153702790

image-20210819143711589

image-20210819144002411

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

image-20210819144145085

image-20210819144320736

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

image-20210819145158768

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

image-20210819145336657

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

weblogic 啟動的時候,會調用 WeblogicObjectInputFilter 的 initializeInternal 方法進行初始化,首先會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 的時候,是 getInternalObjectInputFilter 和 setInternalObjectInputFilter,JDK 9 以及以上是 getObjectInputFilter 和 setObjectInputFIlter 。

2.通過調用 Config.setObjectInputFilter :

image-20210818183207167

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

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 用來表示一個對象,像 RegistryImpl 的 id就比較特殊是 0 ,其他普通對象的 id 都是隨機的:

image-20210827200211131

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

ObjectTable#putTarget 方法:

image-20210827200428621

ObjectTable#getTarget 方法:

image-20210827200436621

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

image-20210827200447422

RegistryImpl 對象與 JEP 290

RegistryImpl 作為一個特殊的對象,導出在 RMI 服務端,客戶端調用的 bind , lookup,list 等操作,實際上是操作 RegistryImpl 的 bindings 這個 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對象,將 RegistryImpl 的 id(OBJID.REGISTRY_ID,也就是 0 ) 存入到 LiveRef 對象,隨后 LiveRef對象賦值到 UnicastServerRef 對象中的 ref 字段,并且將 RegsitryImpl::registryFilter 賦值給這個 UnicastServerRef 對象的 filter 字段:

image-20210823031100532

RegistryImpl 的 id 是 0 :

image-20210827105704572

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

image-20210823031307758

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

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 ,就會設置 ConnectionInputStream的 serialFilter 字段值為 UnicastServerRef.filter (設置單個 ObjectInputStream 的 serialFilter 屬性,局部過濾的體現):

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 對象的時候,會傳入 permanent 為 true :

image-20210825151132318

image-20210825151146319

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

image-20210825151220963

image-20210825151228499

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

image-20210825151048159

也就是說在 createRegistry 的時候,會把 DGCImpl 和 RegistryImpl 封裝的 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#checkInput和 RegistryImpl#registryFilter函數類似,會先判斷 DGCImpl#dgcFilter 字段是否為 null 來決定使用用戶自定義的過濾規則,還是使用默認的白名單規則,如果不是 null 的話,會先調用用戶自定義的過濾規則進行檢查,接著判斷檢查結果,如果不是 UNDECIDED 就直接返回檢查的結果,否則再使用默認的白名單檢查。

RMI 中 JEP 290 的繞過

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

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

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

首先由于 skel 為 null ,所以不會進入 oldDispatch , 像 RegistryImpl 和 DGCImpl 因為他們的 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 方法,而 in 的 serialFilter在這里是為 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 對象中的 serialFilter 是 null。

image-20210827173943082

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

image-20210827174102324

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

image-20210827174304598

總結

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

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

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

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/