作者:kejaly@白帽匯安全研究院
校對:r4v3zn@白帽匯安全研究院
前言
在研究高版本 JDK 反序列化漏洞的時候,往往會涉及到 JEP 290 規范。但是網上公開針對 JEP 290 規范原理研究的資料并不是很多,這就導致在研究高版本 java 反序列化的時候有些無能為力,所以最近對 JEP 290 規范好好的研究的一番,輸出這篇文章,希望和大家一起交流學習。
簡介
官方描述:Filter Incoming Serialization Data,即過濾傳入的序列化數據。

主要內容有:
- 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():


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


serialFilter 屬性

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

filterCheck 函數

filterCheck 函數邏輯可以分三步。
第一步,先會判斷 serialFilter 屬性值是否為空,只有不為空,才會進行后續的過濾操作。
第二步,將我們需要檢查的 class ,以及 arryLength等信息封裝成一個FilterValues對象,

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

最后一步,判斷 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接口的結構:

有一個 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 賦值:

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


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

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

可以看到會拿到 jdk.serailFilter 屬性值,如果不為空,會返回 createFilter(var0)的結果(createFilter 實際返回的是一個 Global 對象)。
jdk.serailFilter 屬性值獲取的方法用兩種,第一種是獲取 JVM 的 jdk.serialFilter 屬性,第二種通過在 %JAVA_HOME%\conf\security\java.security 文件中指定 jdk.serialFilter 來設置。另外從代碼中可以看到,優先選擇第一種。
Config#createFilter 方法

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

Config 類的靜態塊,會賦值 Config.configuredFilter 到 Config.serialFilter 上。
Config#getSerialFilter 方法

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

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

比如可以通過配置 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 上。

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

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

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

Global 類的構造函數:



具體就是通過 filters add 添加 lambdd 表達式到 filters 中,也就是說對 Global 的 filters 賦值的是一個個 lambada 函數。
Global#createFilter 方法
傳入規則字符串,來實例化一個 Global 對象。

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() 。


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

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

所以,這里 Config.serialFilter 值默認是解析 jdk.serailFilter 屬性得到得到的 Global 對象。
weblogic 全局過濾器
在 weblogic 啟動的時候,會賦值 Config.serialFilter 為 WebLogicObjectInputFilterWrapper 。
具體流程如下:
首先在 weblogic 啟動的時候,先調用WeblogicObjectInputFilter.initializeInternal 方法,在 initializeInternal 方法中會先 new一個 JreFilterApiProxy 對象,這個對象是一個進行有關 JEP 290 操作的代理對象(具體原理是通過反射來調用的)。

隨后 new 一個 WeblogicFilterConfig 對象。

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

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



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


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

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

用一段話來闡述 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 方法:

注:低于 JDK 9 的時候,是 getInternalObjectInputFilter 和 setInternalObjectInputFilter,JDK 9 以及以上是 getObjectInputFilter 和 setObjectInputFIlter 。
2.通過調用 Config.setObjectInputFilter :

局部過濾器典型的例子是 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 中:

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

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

ObjectTable#getTarget 方法:

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

RegistryImpl 對象與 JEP 290
RegistryImpl 作為一個特殊的對象,導出在 RMI 服務端,客戶端調用的 bind , lookup,list 等操作,實際上是操作 RegistryImpl 的 bindings 這個 Hashtable。

bind:

lookup:

list:

這里我們之所以稱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。

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

導出過程
首先 LocateRegistry.createRegsitry:


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

RegistryImpl 的 id 是 0 :

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

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

隨后調用 LiveRef.exportObject :

會調用 TCPEndpoint.export:

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

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


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

隨后調用 UnicastServerRef.dispatch:

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

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

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

再看 RegistryImpl_skel.dispatch :

我們以 bind 為例來講解:

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 有以下特征:

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

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


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


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

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

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

通過 JVM 參數或者配置文件進行配置
對于 RegistryImpl
在 RegistryImpl 中含有一個靜態字段 registryFilter ,所以在 new RegistryImpl對象的時候,會調用 initRegistryFilter 方法進行賦值:

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


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

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

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


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 文件:

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

RMI 中 JEP 290 的繞過
網上公開資料廣泛說的是:如果服務端"綁定"了一個對象,他的方法參數類型是Object 類型的方法時,則可以繞過 JEP 290。
其實剖析本質,是因為服務端導出的這個 ”普通的對象“ 對應的 Target 對象中的 disp (其實是 UnicastServerRef 對象) 的 filter 是 null 。

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

下面我們來具體跟以下流程分析,首先準備客戶端和服務端代碼如下:
服務端和客戶端共同包含接口的定義和實現:


服務端代碼如下:

惡意客戶端代碼如下:

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


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

進而調用 UnicastRemoteObject.exportObject 方法:


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

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

接著會調用 UnicastServerRef.exportObject 方法:

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

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

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

然后取出 disp ,調用 disp.dispatch :

首先由于 skel 為 null ,所以不會進入 oldDispatch , 像 RegistryImpl 和 DGCImpl 因為他們的 skel 不為 null ,所以會進入到 oldDispatch:

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

unmarshalCustomCallData 方法:

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

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

反制
利用上面這種方法繞過 JEP 290 去攻擊 RMI 服務端,網上有一些工具,比如 rmitast 和 rmisout 。
但是對于使用 rmitast 或者 rmisout 這些工具,或者調用 lookup() 來試圖攻擊RMI 服務端 的時候,我們可以使用 如下的惡意服務端代碼進行反制:

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

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

同理 RegistryStub.list 也是如此:

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

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

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

首先 Enumerate.connect(this.registry) 返回的實際上是 RegistryImpl_Stub 對象,底層調用的是 LocateRegistry.getRegistry 方法。
然后調用 this.registry.loadObjects(), this.list() 實際調用的是 RegistyImpl_Stub.list() 方法,得到注冊中心的所有綁定的對象名:

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

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

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

總結
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系列」神奇的函數式接口
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1689/
暫無評論