在前面其實有講到過RMI,可是隻是簡單描述了一下RMI反序列化漏洞的利用。可是RMI底層的實現以及原理等方面並無去涉及到,以及RMI的各類攻擊方式。在其餘師傅們的文章中發現RMI的攻擊方式不少。 因此在此去對RMI的底層作一個分析,後面再去對各類攻擊方式去作一個瞭解。html
RPC(Remote Procedure Call)—遠程過程調用,它是一種經過網絡從遠程計算機程序上請求服務,而不須要了解底層網絡技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,爲通訊程序之間攜帶信息數據。在OSI網絡通訊模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網絡分佈式多程序在內的應用程序更加容易。RPC採用客戶機/服務器模式。請求程序就是一個客戶機,而服務提供程序就是一個服務器。java
小總結:數據庫
在最原始數據通信中其實仍是歸根到TCP/UDP協議,可是自使用了RPC後能夠不須要了解底層網絡協議,可是底層仍是經過TCP/UDP去進行網絡調用的。 其實RPC也只是遠程方法調用的統稱,重點在於方法調用中。而RMI實現就是Java版的一個RPC實現。編程
JMS:Java 消息服務(Java Messaging Service) 是一種容許應用程序建立、發送、接受和讀取消息的Java API。JMS 在其中扮演的角色與JDBC 很類似, JDBC 提供了一套用於訪問各類不一樣關係數據庫的公共API,JMS 也提供了獨立於特定廠商的企業消息系統訪問方式。安全
JMS 的編程過程很簡單,歸納爲:應用程序A 發送一條消息到消息服務器(也就是JMS Provider)的某個目的地(Destination),而後消息服務器把消息轉發給應用程序B。由於應用程序A 和應用程序B 沒有直接的代碼關連。服務器
RPC(Remote Procedure Call Protocol)遠程過程調用協議,它是一種經過網絡從遠程計算機程序上請求服務,而不須要了解底層網絡技術的協議。RPC不依賴於具體的網絡傳輸協議,tcp、udp等均可以。網絡
RPC是跨語言的通訊標準。架構
RMI能夠被看做SUN對RPC的Java版本的實現,固然也還有其餘的RPC併發
微軟的DCOM就是創建在ORPC協議之上實現的RPC。異步
RMI集合了Java序列化和Java遠程方法協議(Java Remote Method Protocol)。這裏的Java遠程方法協議則是JRMP。
本段取自RMI與RPC的區別
由此得知其實RMI協議是發送方法以及方法參數,請求到server端後,server進行執行後返回結果給client端。
根據上面內容其實能夠總結爲一句話,RPC是爲了隱藏網絡通訊過程當中的細節,方便使用。而在RMI中也是同樣的。RMI中爲了隱藏網絡通信的過程細節採用了動態代理的方式來進行實現。
下面先來看到調用流程圖
在客戶端和服務器各有一個代理,客戶端的代理叫Stub(存根),服務端的代理叫Skeleton(骨架),合在一塊兒造成了 RMI 構架協議,負責網絡通訊相關的功能。代理都是由服務端產生的,客戶端的代理是在服務端產生後動態加載過去的。
stub擔當遠程對象的客戶本地表明或代理人角色,負責把要調用的遠程對象方法的方法名及其參數編組打包,並將該包轉發給遠程對象所在的服務器。
Stub編碼後發送的數據包內容,包含以下內容:
1. 被使用的遠程對象的標識符 2. 被調用的方法的描述 3. 編組後的參數
Skeleton接收到Stub發送數據會執行以下操做:
1. 從數據包中定位要調用的遠程對象 2. 調用所需的方法,並傳遞客戶端提供的參數 3. 捕獲返回值或調用產生的異常。 4. 將打包返回值編組,返回給客戶端Stub
本段內容部分取自:RMI反序列化漏洞分析
總結大致的內容以下:
客戶端(Client):服務調用方。 客戶端存根(Client Stub):存放服務端地址信息,將客戶端的請求參數數據信息打包成網絡消息,再經過網絡傳輸發送給服務端。 服務端存根(Server Stub):接收客戶端發送過來的請求消息並進行解包,而後再調用本地服務進行處理。 服務端(Server):服務的真正提供者。
這裏值得注意的一點是前面說到的傳輸進行打包和解包的步驟其實就是序列化和反序列化,傳輸的是序列化的數據。
RMI大體的遠程調用執行流程:
1. 客戶端發起請求,請求轉交至RMI客戶端的stub類; 2. stub類將請求的接口、方法、參數等信息進行序列化; 3. 基於socket將序列化後的流傳輸至服務器端; 4. 服務器端接收到流後轉發至相應的Skeleton類; 5. Skeleton類將請求的信息反序列化後調用實際的處理類; 6. 處理類處理完畢後將結果返回給Skeleton類; 7. Skelton類將結果序列化,經過socket將流傳送給客戶端的stub; 8. stub在接收到流後反序列化,將反序列化後的Java Object返回給調用者。
server在遠程機器上監聽一個端口,這個端口是jvm或者os在運行時隨機選擇的一個端口。能夠說server在遠程機器上在這個端口上導出本身。
client並不知道server在哪,以及sever監聽哪一個端口,可是他有stub。stub知道全部這些東西,這樣client能夠調用stub上他想調用的任何方法。
client調用給你stub上的方法
stub連接server監聽的端口併發送參數,詳細過程以下:
4.1 client鏈接server監聽的端口
4.2 server收到請求並建立一個socket來處理這個鏈接
4.3 server繼續監聽到來的請求
4.4 使用雙方協定的歇息,傳送參數和結果
4.5 協議能夠是JRMP或者 iiop
方法在遠程server上執行,併發執行結果返回給stub
stub返回結果給client,就好像是stub執行了這個方法同樣。
其實也是同樣對於了下面的這張圖
可是第二部份內容是值得思索的一點,爲何client不知道server的監聽端口和server在哪,而stub卻知道呢?client不知道server的host和port的話,stub是如何建立一個知道全部這一切的stub對象呢?
這時候就引出了RMIRegistry(註冊中心)的做用了。
RMIRegistry 能夠認爲是一個服務,它提供了一個hashmap,裏面是 public_name
, Stub_object
名值對。好比有一個遠程服務對象叫作 Scientific_Calculator
,而後想把這個服務對外公佈爲 calc,這樣會在server上建立一個stub對象,把他註冊到RMIRegistry ,這樣client就能夠從RMIRegistry 中獲得這個stub對象了,可使用一個工具類java.rmi.Naming
來方便的操做註冊和操做。
實現可看Java安全之RMI反序列化該篇文章。
簡單來講就是使用java.rmi.Naming
將一個某一個類註冊進RMIRegistry 裏面,註冊後會在server端建立stub對象,而client就能夠從RMIRegistry 中獲得這個stub對象。前面內容說到過代理都是由服務端產生的,客戶端的代理是在服務端產生後動態加載過去的。RMIRegistry運行在server端當中。
首先RMIRegistry 運行在server端,RMIRegistry 自身也是一個遠程對象。有一點須要注意的是:全部的遠程對象(繼承了UnicastRemoteObject對象)都會在sever上任意的端口導出本身,由於RMIRegistry 也是一個遠程對象,他也在server上導出本身,這個端口是1099。
服務端運行在server上,在UnicastRemoteObject構造函數裏面,他把本身導出在server上一個任意端口上,這個端口client是不知道的
當你調用Naming.rebind()的時候,會傳入一個CalcImpl 的引用(這裏叫作 c)做爲第2個參數,Naming 就會構造一個stub對象,詳細以下:
a. Naming會使用getClass來獲取類的名字,這裏就是CalcImpl
b. 加上後綴_Stub 變成了 CalcImpl _Stub
c. 加載CalcImpl_Stub.class到虛擬機中
d. 從c中獲取RemoteRef 對象
e. 就是這個RemoteRef 對象中封裝了服務端細節,包括服務端的hostname、port
f. 獲取了RemoteRef 對象以後,就能夠構造stub對象了。
g. 傳遞stud對象到RMIRegistry中進行綁定,即(publicname,stub)
f. RMIRegistry 中內部使用一個hashmap來存儲(publicname,stub)
當客戶端使用 Naming.lookup()的時候,會傳入public name 做爲參數,RMIRegistry 就會返回stub給客戶端調用
這裏做者說的導出本身
這個沒太理解這個的意思,個人理解是映射,將內容映射到1099端口中。
整體來講就是因此的遠程對象都會在server的任意的端口上映射,RMIRegistry 也會進行映射,可是RMIRegistry 映射的端口是1099(默認是,能夠修改)。遠程對象進行映射的端口,client是不知道的。可是stub對象會知道。
在調用Naming.rebind()並而且傳入某一個引用的時候,Naming 就會構造一個stub對象。Naming內部採用getClass來獲取類的名字,而且添加_Stub
後綴後價值到虛擬機中,而後從引用中獲取 RemoteRef 對象,該對象就封裝了封裝了服務端的細節。而獲取到該RemoteRef 就能夠構造stub對象了,構造完成後傳遞到RMIRegistry註冊中心中進行綁定,內部採用hashmap鍵值對的方式,即(publicname,stub),這時候使用Naming.lookup()傳入對應的方法名,則會返回對應的stub到client端。
RMIRegistry 存在的意義只是爲了方便client獲取到stub對象,stub構造函數中須要一個RemoteRef 對象,這個對象只能在server端獲取。
本段內容部分摘取自:深刻理解rmi原理
簡單的分析了一下RMI底層的架構,可是這些其實都僅僅是基於概念和理論層面的,具體的代碼實現其實還沒去看。在其中也是看得暈頭轉向的,部分也摘取了其餘師傅們的文章內容,感受已經總結很到位了,瘋狂安利。摘取的內容下也貼出來 摘取內容的出處,感謝各位師傅們的詳細講解。