【入坑JAVA安全】RMI基礎看這一篇就足夠了

0x01基本概念

RMI的全稱是Rmote Method Invocation,即遠程方法調用,具體怎麼實現呢?遠程服務器提供具體的類和方法,本地會通過某種方式獲得遠程類的一個代理,然後通過這個代理調用遠程對象的方法,方法的參數是通過序列化與反序列化的方式傳遞的,所以,1. 只要服務端的對象提供了一個方法,這個方法接收的是一個Object類型的參數,2. 且遠程服務器的classpath中存在可利用pop鏈,那麼我們就可以通過在客戶端調用這個方法,並傳遞一個精心構造的對象的方式來攻擊rmi服務。

0x02 實現機制

上面說了本地會通過某種方式獲得遠程對象的代理,那麼具體是怎麼的實現機制呢?RMI模式中除了有Client與Server,還藉助了一個Registry(註冊中心)。

Server Registry Client
提供具體的遠程對象 一個註冊表,存放着遠程對象的位置(ip、端口、標識符) 遠程對象的使用者

其中Server與Registry可以在同一服務器上實現,也可以佈置在不同服務器上,現在一個完整的RMI流程可以大概描述爲:

  1. Registry先啓動,並監聽一個端口,一般爲1099
  2. Server向Registry註冊遠程對象
  3. Client從Registry獲得遠程對象的代理(這個代理知道遠程對象的在網絡中的具體位置:ip、端口、標識符),然後Client通過這個代理調用遠程方法,Server也是有一個代理的,Server端的代理會收到Client端的調用的方法、參數等,然後代理執行對應方法,並將結果通過網絡返回給Client。

兩圖勝千言:

整體流程:

在這裏插入圖片描述
ps: 圖中的stub就是客戶端代理,skeleton就是服務端代理,老外起的這英文名字我實在是理解不了~

遠程方法調用的通信模式:

在這裏插入圖片描述

不知道有沒有人和我一樣想過爲什麼需要這個註冊表?

0x03 代碼實現

我們已經知道了大體的流程了,那麼用代碼如何實現上述流程呢?我們自己動手創建一個項目吧,項目結構如下:

在這裏插入圖片描述

1.首先創建一個接口Hello:

package model;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
    public String welcome(String name) throws RemoteException;
}

2.基於這個接口實現一個類Helloimpl:

package model.impl;

import model.Hello;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class Helloimpl extends UnicastRemoteObject implements Hello {
    public Helloimpl() throws RemoteException {
    }

    @Override
    public String welcome(String name) throws RemoteException {
        return "Hello, "+name;
    }
}

3.創建服務端,服務端創建了一個註冊表,並註冊了客戶端需要的對象

package server;

import model.Hello;
import model.impl.Helloimpl;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Server {
    public static void main(String[] args) throws RemoteException{
        // 創建對象
        Hello hello = new Helloimpl();
        // 創建註冊表
        Registry registry = LocateRegistry.createRegistry(1099);
        // 綁定對象到註冊表,並給他取名爲hello
        registry.rebind("hello", hello);
    }
}

4.客戶端調用遠程對象

package client;
import model.Hello;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {
    public static void main(String[] args) throws RemoteException, NotBoundException {
        // 獲取到註冊表的代理
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);
        // 利用註冊表的代理去查詢遠程註冊表中名爲hello的對象
        Hello hello = (Hello) registry.lookup("hello");
        // 調用遠程方法
        System.out.println(hello.welcome("axin"));
    }
}

先啓動服務端,在啓動客戶端,客戶端成功執行遠程方法並獲得返回的數據:

在這裏插入圖片描述

在寫代碼的時候有幾點需要注意:

  1. 接口需要集成Remote接口,且方法需要拋出RemoteException錯誤
  2. 接口的實現類需要繼承UnicastRemoteObject,同樣的方法需要拋出RemoteException錯誤
  3. 如果遠程方法需要傳參,需要保證參數是可序列化的,我這裏傳參只是傳了字符串,字符串是可序列化的,如果傳參是自定義的對象,那麼這個對象需要實現Serilizable接口

注意一點,由於我這裏服務端與客戶端都是在一臺機器上實現的,所以看起來比較簡單,如果服務端與客戶端不在同一主機,需要保證調用的遠程對象實現的那個接口在客戶端與服務端都存在!

0x04 其他

到這裏就差不多了,暫時滿足我們後續學習rmi反序列化漏洞的需要,如果好奇rmi底層代碼實現,可以再去讀一下jdk的源碼,這樣會加深你對rmi的理解~

放一篇從源碼層面解析rmi實現的文章:https://xz.aliyun.com/t/2223

看文章的同時,最好結合着實踐

關注我的公衆號,一起玩耍

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章