Java中怎么遠(yuǎn)程調(diào)用RMI,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。
蒼溪ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
JNDI(Java Naming and Directory Interface,Java命名和目錄接口)是SUN公司提供的一種標(biāo)準(zhǔn)的Java命名系統(tǒng)接口,JNDI提供統(tǒng)一的客戶端API,通過不同的訪問提供者接口JNDI服務(wù)供應(yīng)接口(SPI)的實(shí)現(xiàn),由管理者將JNDI API映射為特定的命名服務(wù)和目錄系統(tǒng),使得Java應(yīng)用程序可以和這些命名服務(wù)和目錄服務(wù)之間進(jìn)行交互。
JRMP
Java遠(yuǎn)程方法協(xié)議(英語:Java Remote Method Protocol,JRMP)是特定于Java技術(shù)的、用于查找和引用遠(yuǎn)程對象的協(xié)議。這是運(yùn)行在Java遠(yuǎn)程方法調(diào)用(RMI)之下、TCP/IP之上的線路層協(xié)議。
RMI
Java遠(yuǎn)程方法調(diào)用,即Java RMI(Java Remote Method Invocation)是Java編程語言里,一種用于實(shí)現(xiàn)遠(yuǎn)程過程調(diào)用的應(yīng)用程序編程接口。它使客戶機(jī)上運(yùn)行的程序可以調(diào)用遠(yuǎn)程服務(wù)器上的對象。遠(yuǎn)程方法調(diào)用特性使Java編程人員能夠在網(wǎng)絡(luò)環(huán)境中分布操作。RMI全部的宗旨就是盡可能簡化遠(yuǎn)程接口對象的使用。
JDK關(guān)鍵版本
RMI Serialization Attack
注意:此Demo沒有版本限制,但部分邏輯會由于版本原因造成出入。
Demo
with JDK 1.8.0_151
with java-rmi-server/ rmi.RMIServer、Services、PublicKnown
with java-rmi-client/ rmi.RMIClient、Services、ServicesImpl、PublicKnown
PS:低版本無法在RegistryImpl_Skel下有效斷點(diǎn)。
分析
兩種 bind 區(qū)別
Server <-> RMI Registry <-> Client
server 通過 bind 注冊服務(wù)時會進(jìn)行序列化傳輸服務(wù)名&Ref,因此會進(jìn)入RegistryImpl_Skel.dispatch先經(jīng)過反序列化獲取。
Server(RMI Registry) <-> Client
這種模式下,由于 server 與 Registry 是同一臺機(jī)器,在 bind 注冊時由于 server 上已有其 Ref,因此不需要序列化傳輸,只需要在 bindings list 中添加對應(yīng)鍵值即可。
注冊、請求流程
RMI Registry 的核心在于 RegistryImpl_Skel。當(dāng)Server執(zhí)行bind、Client執(zhí)行l(wèi)ookup時候,均會通過sun.rmi.registry.RegistryImpl_Skel#dispatch進(jìn)行處理。
bind
首先注意到ServiceImpl繼承了UnicastRemoteObject,在實(shí)例化時會通過exportObject創(chuàng)建返回此服務(wù)的stub。
public class ServiceImpl extends UnicastRemoteObject implements Service {...}/*** Exports the specified object using the specified server ref.*/private static Remote exportObject(Remote obj, UnicastServerRef sref)throws RemoteException{// if obj extends UnicastRemoteObject, set its ref.if (obj instanceof UnicastRemoteObject) {((UnicastRemoteObject) obj).ref = sref;}return sref.exportObject(obj, null, false);}
再通過bind向RMI Registry服務(wù)器申請注冊綁定服務(wù)名&stub跟入到sun.rmi.registry.RegistryImpl_Stub#bind,注意觀察到向RMI Registry申請時,第三個參數(shù)對應(yīng) operations 里的操作。
這里尤其注意的兩個 writeObject,分別向 var3 的輸出流中寫入序列化后的服務(wù)名&stub。
RMI Registry收到申請時會進(jìn)行會通過傳入的操作值進(jìn)入相關(guān)流程,0時進(jìn)入bind,注意到兩次 readObject 分別反序列化獲取服務(wù)名&stub后,再向 bindings List 中寫入鍵值。
這里就引出來了一個點(diǎn):Server 通過向 RMI Registry 申請 bind 操作進(jìn)行序列化攻擊。
lookup
再看Client向RMI Registry申請lookup 查找時候(sun.rmi.registry.RegistryImpl_Stub#lookup)傳遞的操作數(shù)為 2,且反序列化了目標(biāo)服務(wù)名。
RMI
Registry(sun.rmi.registry.RegistryImpl_Skel#dispatch)這邊同樣會先反序列化獲取查詢服務(wù)名,再從 bindings list 中進(jìn)行查詢。
這里就引出來了另一個點(diǎn):Client 通過向 RMI Registry 申請 lookup 操作進(jìn)行序列化攻擊。
但是就完了么?
我們再往下看,注意到 86 行出現(xiàn)的 writeObject,這里是將查詢到的stub序列化傳輸給 Client。
回到 Client 的代碼中,可以看到104 行的 readObject。
這里就引出來了第三個點(diǎn):RMI Registry 通過 lookup 操作被動式攻擊 Client。
調(diào)用時序列化
現(xiàn)在我們理清了bind、lookup的部分內(nèi)容,那么 client 是如何實(shí)現(xiàn)遠(yuǎn)程調(diào)用呢?
通過跟進(jìn)后可以看到由
java.rmi.server.RemoteObjectInvocationHandler實(shí)現(xiàn)的動態(tài)代理,并最終由sun.rmi.server.UnicastRef#invoke實(shí)現(xiàn)調(diào)用。
在調(diào)用中我們注意到通過marshalValue打包參數(shù),由unmarshalValue對傳回的內(nèi)容進(jìn)行反序列化。
限制
這里的 Demo 實(shí)際情況中很難遇到,因?yàn)閑vil是我們根據(jù)已知的Services、PublicKnown(含已知漏洞)生成的,在攻擊時更多都是采用本地 gadget。
攻擊方向
注意到我們上面提出了三個攻擊向。
1.Server 通過向 RMI Registry 申請 bind 操作進(jìn)行序列化攻擊;
2.Client 通過向 RMI Registry 申請 lookup 操作進(jìn)行序列化攻擊;
3.RMI Registry 通過 lookup 操作被動式攻擊 Client。
其實(shí)注意到第一個點(diǎn)里提到的 Server 并不是要求一定要由目標(biāo)服務(wù)器發(fā)起,比如任意一臺(包括攻擊者)均可以向注冊中心發(fā)起注冊請求進(jìn)而通過 bind 在 RMI Registry 上進(jìn)行攻擊,例如:
Client -- bind --> RMI Registry(Server)
同理第二點(diǎn)、第三點(diǎn)里也是,所以我們更新一下:
1.向 RMI Registry 申請 bind 操作進(jìn)行序列化攻擊;
2.向 RMI Registry 申請 lookup 操作進(jìn)行序列化攻擊;
3.RMI Registry通過lookup操作被動式序列化攻擊請求者。
bind - RMIRegistryExploit
with JDK 1.7.0_17
with java-rmi-server/ rmi.RMIServer2
with ysoserial.exploit.RMIRegistryExploit
ysoserial.exploit.RMIRegistryExploit實(shí)際對應(yīng)bind攻擊方向,我們來簡單看下它的代碼。
核心在于兩點(diǎn),對于第一點(diǎn)可以看看 cc1 分析以及Java動態(tài)代理-實(shí)戰(zhàn)這篇。
sun.reflect.annotation.AnnotationInvocationHandler動態(tài)代理Remote.class
bind 操作
這里提一下為什么需要動態(tài)代理,是由于在sun.rmi.registry.RegistryImpl_Skel#dispatch,執(zhí)行bind時會通過Remote.readObject反序列化,導(dǎo)致調(diào)用
AnnotationInvocationHandler.invoke。
codebase傳遞以及useCodebaseOnly
RMI有一個重要的特性是動態(tài)類加載機(jī)制,當(dāng)本地CLASSPATH中無法找到相應(yīng)的類時,會在指定的codebase里加載class,
需要java.rmi.server.useCodebaseOnly=false,但是這個特性是一直開啟的,直到6u45、7u21修改默認(rèn)為 true 以防御攻擊。
這里引用官方文檔 Enhancements in JDK 7:
如果RMI連接一端的JVM在其java.rmi.server.codebase系統(tǒng)屬性中指定了一個或多個URL,則該信息將通過RMI連接傳遞到另一端。如果接收方JVM的java.rmi.server.useCodebaseOnly系統(tǒng)屬性設(shè)置為false,則它將嘗試使用這些URL來加載RMI請求流中引用的Java類。
從由RMI連接的遠(yuǎn)程端指定位置加載類的行為,當(dāng)被禁用
java.rmi.server.useCodebaseOnly被設(shè)定為true。在這種情況下,僅從預(yù)配置的位置(例如本地指定的
java.rmi.server.codebase屬性或本地CLASSPATH)加載類,而不從codebase通過RMI請求流傳遞的信息中加載類。
demo
Client 攻擊 Server
with JDK 1.7.0_17
with java-rmi-server/rmi.RMIServer2
with java-rmi-client/rmi.RMIClient2、remote.RemoteObject
若 Client 指定了 codebase 地址,Server 加載目標(biāo)類時會現(xiàn)在本地 classpath 中進(jìn)行查找,在沒有找到的情況下會通過 codebase 對指定地址再次查找。
為了能夠遠(yuǎn)程加載目標(biāo)類,需要Server加載并配置RMISecurityManager,并同時設(shè)置:
java.rmi.server.useCodebaseOnly=false
在傳輸了 codebase 之后是如何調(diào)用的呢?
也是由動態(tài)代理類
java.rmi.server.RemoteObjectInvocationHandler#invokeRemoteMethod實(shí)現(xiàn)遠(yuǎn)程調(diào)用。
Server 接收到調(diào)用指令后,進(jìn)入
sun.rmi.server.MarshalInputStream#resolveClass,
由于 useCodebaseOnly 為 false,從客戶端指定地址遠(yuǎn)程讀取目標(biāo)類。
全部讀取完畢后回到
java.io.ObjectInputStream#readOrdinaryObject,
調(diào)用
java.io.ObjectStreamClass#initNonProxy進(jìn)行實(shí)例化。
Server 攻擊 Client
with JDK 1.7.0_17
with java-rmi-server/rmi.RMIServer3、remote.RemoteObject2
with java-rmi-client/rmi.RMIClient3
可以對比看到,從sun.rmi.server.UnicastRef#invoke起是一致的邏輯,只是上層調(diào)用來源不一樣,不再贅述。
區(qū)別攻擊方向
方法調(diào)用請求均來自 Client。
但區(qū)別的產(chǎn)生在于
sun.rmi.server.UnicastRef#invoke(java.rmi.Remote,java.lang.reflect.Method,java.lang.Object[], long)處的邏輯代碼。
line 79: Client 攻擊 Server,在于讓 Server 請求遠(yuǎn)程 Class 產(chǎn)生結(jié)果,由于本地同名惡意類安全所以不會對本地造成攻擊。
line 89: Server 攻擊 Clinet,在于 Client 獲取到安全結(jié)果后需要獲取遠(yuǎn)程 Class 進(jìn)行本地反序列化導(dǎo)致被攻擊。
with JDK 1.7.0_80
with java-rmi-server/rmi.RMIServer2
看情況取舍:
上面說的RMI通信過程中假設(shè)客戶端在與RMI服務(wù)端通信中,雖然也是在JRMP協(xié)議上進(jìn)行通信,嘗試傳輸序列化的惡意對象到服務(wù)端,此時服務(wù)端若也返回客戶端一個惡意序列化的對象,那么客戶端也可能被攻擊,利用JRMP就可以利用socket進(jìn)行通信,客戶端直接利用JRMP協(xié)議發(fā)送數(shù)據(jù),而不用接受服務(wù)端的返回,因此這種攻擊方式也更加安全。
這里我們針對 ysoserial 的幾個相關(guān) Class 進(jìn)行分析,首先先列舉下相關(guān)的作用。
payloads.JRMPListener 在目標(biāo)服務(wù)器目標(biāo)端口上開啟JRMP監(jiān)聽服務(wù) - 獨(dú)立利用
payloads.JRMPClient 向目標(biāo)服務(wù)器發(fā)送注冊 Ref,目標(biāo) exploit.JRMPListener 地址
exploit.JMRPListener 被動向請求方傳輸序列化 payload
exploit.JRMPClient 主動向目標(biāo)服務(wù)器傳輸序列化 payload
除此之外,我們還需要了解下關(guān)于DGC的一些內(nèi)容,以便理解下面的內(nèi)容。
RMI.DGC 為 RMI 分布式垃圾回收提供了類和接口。當(dāng) RMI 服務(wù)器返回一個對象到其客戶機(jī)(遠(yuǎn)程方法的調(diào)用方)時,其跟蹤遠(yuǎn)程對象在客戶機(jī)中的使用。當(dāng)再沒有更多的對客戶機(jī)上遠(yuǎn)程對象的引用時,或者如果引用的“租借”過期并且沒有更新,服務(wù)器將垃圾回收遠(yuǎn)程對象。
payloads.JRMPListener
在了解之前,我們先看下JAVA原生序列化有兩種接口實(shí)現(xiàn)。
1.Serializable接口:要求實(shí)現(xiàn)writeObject、readObject、writeReplace、readResolve
2.Externalizable接口:要求實(shí)現(xiàn) writeExternal、readExternal
分析
回到JRMPListener中,代碼很簡單,主要功能就是生成一個開啟目標(biāo)端口進(jìn)行監(jiān)聽RMI服務(wù)的payload。
我們首先跟入到
ysoserial.payloads.util.Reflections#createWithConstructor,了解下函數(shù)邏輯。
1.先查找RemoteObject下參數(shù)類型為 RemoteRef 的構(gòu)造器。
2.根據(jù)找到的構(gòu)造器為ActivationGroupImpl動態(tài)生成一個新的構(gòu)造器并生成實(shí)例。
為什么需要這樣呢?其實(shí)就是為了避免調(diào)用ActivationGroupImpl本身的構(gòu)造方法,避免復(fù)雜的或其他不可控的問題。
我們關(guān)注下UnicastRemoteObject在序列化階段做了什么,從reexport跟入到exportObject,創(chuàng)建監(jiān)聽并返回此 stub。
另外,通過上面的分析實(shí)際上我們只用需要UnicastRemoteObject就足夠開啟監(jiān)聽利用,下面兩種也可以,但好奇為什么作者要通過子類轉(zhuǎn)換實(shí)現(xiàn)利用呢?
ActivationGroupImpl uro = Reflections.createWithConstructor(ActivationGroupImpl.class, RemoteObject.class, new Class[] {RemoteRef.class}, new Object[] {new UnicastServerRef(jrmpPort)});UnicastRemoteObject uro = Reflections.createWithConstructor(UnicastRemoteObject.class, RemoteObject.class, new Class[] {RemoteRef.class}, new Object[] {new UnicastServerRef(jrmpPort)});
利用
java -cp ysoserial-master.jar ysoserial.exploit.XXXXXJRMPListener java -cp ysoserial-master.jar ysoserial.exploit.JRMPClient
payloads.JRMPClient
分析
作為 payloads 核心代碼依舊不是很多,生成 ref 并封裝到 handler,動態(tài)代理Registry類。
實(shí)際上,對于 ClassLoader 我們是可以設(shè)置為 Null,這個問題可以通過上面的資料鏈接回答。
至于為什么強(qiáng)轉(zhuǎn)為 Registry ?只是因?yàn)槲覀儎討B(tài)代理了這個類,集成了需要代理類的各種方法,在不調(diào)用這些方法時替換成任意 Object 子類均可。
現(xiàn)在我們看下代碼邏輯:
當(dāng)我們傳遞一個 proxy 準(zhǔn)備序列化時,大意上同樣會對其成員進(jìn)行序列化(這里不展開,需要自己看序列化),所以會調(diào)用其父類 RemoteObject.readObject()
注意到最后會調(diào)用 readExternal 方法,原因已在上文提到。
這里便會調(diào)用
sun.rmi.server.UnicastRef#readExternal,
之后進(jìn)入
sun.rmi.transport.LiveRef#read,
但這里并不能進(jìn)入到 DGCClient 注冊,但會把 ref 信息存入到
ConnectionInputStream.incomingRefTable中。
在最后釋放輸入連接時,會對incomingRefTable中的 ref 進(jìn)行注冊。
為什么要這么做呢?java 注釋寫有,詳細(xì)內(nèi)容沒有查到。
/*** Save reference in order to send "dirty" call after all args/returns* have been unmarshaled. Save in hashtable incomingRefTable. This* table is keyed on endpoints, and holds objects of type* IncomingRefTableEntry.*/
而在sun.rmi.transport.DGCImpl_Skel#dispatch中也是類似注釋中的流程。
回到 ref 注冊,實(shí)際是會在 DGCClient 中對 refs 進(jìn)行注冊。
然后對傳輸過來的數(shù)據(jù)直接進(jìn)行反序列化解析,這里的內(nèi)容放在
exploit.JRMPListener中講解。
所以整個流程分析下來,并沒有看到需要使用動態(tài)代理的地方,因此生成 payload 時直接序列化傳輸RemoteObject子類也就足夠,而原生自帶的容易控制的子類為RemoteObjectInvocationHandler,即:
利用
payloads.JRMPClient 是要配合 exploit.JRMPListener 一起使用的。
java -cp ysoserial-master.jar ysoserial.exploit.JRMPListenerjava -cp ysoserial-master.jar ysoserial.exploit.XXXXX JRMPClient :
exploit.JRMPListener
分清兩個JRMPListener的區(qū)別
payloads.JRMPListener 在目標(biāo)機(jī)上開啟 JMRP 監(jiān)聽
exploit.JRMPListener 實(shí)現(xiàn)對 JRMP Client 請求的應(yīng)答
分析
從 Main 可以看到基本邏輯就是開啟監(jiān)聽 JRMP 端口等待連接后傳輸惡意 payload。
在監(jiān)聽時對協(xié)議進(jìn)行解析,對為 StreamProtocol、SingleOpProtocol 的連接均會通過 doMessage 進(jìn)行應(yīng)答。
而在 doMessage 中對遠(yuǎn)程RMI調(diào)用發(fā)送 payload 數(shù)據(jù)包。
那么 payload 是填充到哪里了呢?
注意到 doCall 函數(shù)中的這段代碼,和 cc5 的入口點(diǎn)是一樣的。
但需要注意的是,BadAttributeValueExpException.readObject的觸發(fā)點(diǎn)不一定是 valObj.toSting(),這里在調(diào)試的時候出現(xiàn)了一堆莫名其妙的現(xiàn)象。
拋開后續(xù)的利用,我們從開始看下目標(biāo)是如何向 JRMPListener 請求的。
會向 DGCClient 中進(jìn)行注冊 Ref,通過80請求、81應(yīng)答進(jìn)行傳輸,這里可以關(guān)注下調(diào)用棧,結(jié)合上面 DGC 內(nèi)容進(jìn)行了解。
那么 80 是如何出現(xiàn)的呢?
看到StreamRemoteCall初始化時會直接往第一個字節(jié)寫入 80。
接著目標(biāo)會讀取 Listener 傳遞的值對之后的內(nèi)容選擇是否進(jìn)行反序列化,反序列化的內(nèi)容就和上面連接起來了。
額外提一下,var1在這里的意義是用來判斷Listener是否為正常返回,如果因?yàn)槟承┰蛟?Listener 端產(chǎn)生了異常報錯需要將報錯信息傳遞回請求端,而傳遞的信息是序列化的所以會在請求端觸發(fā)反序列化。
利用
本身無法直接利用的,需要向目標(biāo)機(jī)發(fā)送 payloads.JRMPClient 以被動攻擊。
java -cp ysoserial-master.jar ysoserial.exploit.JRMPListener
exploit.JRMPClient
分清兩個 JRMPClient 區(qū)別,以及 RMIRegistry
Exploit
payloads.JRMPClient 向目標(biāo)DGC注冊Ref
exploit.JRMPClient 向目標(biāo)DGC傳輸序列化 payload
exploit.RMIRegistryExploit 向目標(biāo)RMI.Registry傳輸序列化 payload,目標(biāo)為 RMI.Registry 監(jiān)聽端口
下面是payloads.JRMPListener和RMI.Registry 開啟的監(jiān)聽端口在nmap掃描下的不同信息:
exploit.JRMPClient 可以對兩者進(jìn)行攻擊;
exploit.RMIRegistryExploit只能攻擊后者。
分析
先在sun.rmi.server.UnicastServerRef#dispatch中讀取 Int 數(shù)據(jù)。
然后在
sun.rmi.server.UnicastServerRef#oldDispatch中讀取 Long 數(shù)據(jù)。
之后進(jìn)入sun.rmi.transport.DGCImpl_Skel#dispatch,先對讀取的 Long 數(shù)據(jù)即接口 hash 值進(jìn)行判斷是否為相同。
再根據(jù)之前讀取的 Int 數(shù)據(jù)進(jìn)行相應(yīng)的處理。
利用
java -cp ysoserial-master.jar ysoserial.exploit.JRMPClient
關(guān)于 JNDI 的內(nèi)容已在整篇文章開頭有涉及,此處暫時無額外需求。
demo
with JDK 1.7.0_17
with jndi\rmi.RMIClient、rmi.RMIServer
分析
我們跟進(jìn)Client執(zhí)行l(wèi)ookup后看看發(fā)生了什么。
同樣也是Client向Server請求查詢test_service對應(yīng)的 stub,再執(zhí)行到 com.sun.jndi.rmi.registry.RegistryContext#decodeObject中獲取目標(biāo)類的 ref。
之后帶入 ref 到
javax.naming.spi.NamingManager#getObjectInstance中進(jìn)行遠(yuǎn)程工廠類的加載(所以Server 端 new Reference 時的第一個 class 參數(shù)隨便寫不影響)。
這樣就是在 Client 執(zhí)行 lookup 操作時讓其直接加載遠(yuǎn)程惡意類進(jìn)行 RCE,不需要任何其他的 gadget。
防御
受到自6u141、7u131、8u121起默配置com.sun.jndi.rmi.object.trustURLCodebase=false,直接遠(yuǎn)程加載會被限制,報錯信息如下:
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對創(chuàng)新互聯(lián)的支持。