基本概念RMI的全称是Rmote Method Invocation,即远程方法调用。 远程服务器提供具体的类和方法,本地会通过某种方式获得远程类的一个代理,然后通过这个代理调用远程对象的方法,方法的参数是通过序列化与反序列化的方式传递的。 所以,1. 只要服务端的对象提供了一个方法,这个方法接收的是一个Object类型的参数,2. 且远程服务器的classpath中存在可利用pop链,那么我们就可以通过在客户端调用这个方法,并传递一个精心构造的对象的方式来攻击rmi服务。 RMI的组成RMI模式中除了有Client与Server,还借助了一个Registry(注册中心)。rmi反序列化攻击就是攻击这个注册中心。 RMI的角色:
RMI的调用过程:
图源参考链接: 代码实现项目结构: 1. 创建接口创建一个接口Hello,该接口需要继承Remote接口,接口所定义的方法需要抛出 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 { @Override public String welcome(String name) throws RemoteException { return "Hello " + name; } public Helloimpl() throws RemoteException{ }} 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")); }} 调用的远程方法如果需要传入参数,需要保证参数是可序列化的。这里传的参数为字符串,可序列化。如果传参是自定义的对象,那么这个对象需要实现Serilizable接口。 先启动服务端,再启动客户端,可以看到客户端已经成功调用远程方法: 如果服务端和客户端在不同机器上,需要保证远程调用对象所实现的那个接口(这里是Hello接口)在服务端和客户端都存在,因为客户端有一个实例化的过程。 动态类加载和安全策略1. 动态类加载如果客户端在调用时,传递了一个可序列化对象,这个对象在服务端不存在,则在服务端会抛出 可使用 2. 安全策略上面的通过网络加载外部类并执行方法,所以我们必须要有一个安全管理器来进行管理,如果没有设置安全管理,则 RMI 不会动态加载任何类,通常我们使用: if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager());} 安全管理器应与管理策略相辅相成,所以我们还需要提供一个策略文件,里面配置允许哪些主机进行哪些操作,这里为了方便测试,直接设置全部权限:
// Standard extensions get all permissions by default grant { permission java.security.AllPermission; }; 同样可以使用 ====================================== 【定义】 Java RMI:Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。 我们知道远程过程调用(Remote Procedure Call, RPC)可以用于一个进程调用另一个进程(很可能在另一个远程主机上)中的过程,从而提供了过程的分布能力。Java 的 RMI 则在 RPC 的基础上向前又迈进了一步,即提供分布式对象间的通讯。 RMI(Remote Method Invocation)为远程方法调用,是允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。 这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。 【JavaRMI】 一、工作原理 RMI能让一个Java程序去调用网络中另一台计算机的Java对象的方法,那么调用的效果就像是在本机上调用一样。通俗的讲:A机器上面有一个class,通过远程调用,B机器调用这个class 中的方法。 RMI,远程方法调用(Remote Method Invocation)是Enterprise JavaBeans的支柱,是建立分布式Java应用程序的方便途径。RMI是非常容易使用的,但是它非常的强大。 RMI的基础是接口,RMI构架基于一个重要的原理:定义接口和定义接口的具体实现是分开的。下面我们通过具体的例子,建立一个简单的远程计算服务和使用它的客户程序 二、RMI包含部分:
三、RMI的用途? RMI的用途是为分布式Java应用程序之间的远程通信提供服务,提供分布式服务。 目前主要应用时封装在各个J2EE项目框架中,例如Spring,EJB(Spring和EJB均封装了RMI技术) 在Spring中实现RMI: ①在服务器端定义服务的接口,定义特定的类实现这些接口; ②在服务器端使用org.springframework.remoting.rmi.RmiServiceExporter类来注册服务; ③在客户端使用org.springframework.remoting.rmi.RmiProxyFactoryBean来实现远程服务的代理功能; ④在客户端定义访问与服务器端服务接口相同的类 四、RMI的局限? RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。JRMP是专为Java的远程对象制定的协议,由于JRMP是专为Java对象制定的,因此,RMI对于用非Java语言开发的应用系统的支持不足。不能与用非Java语言书写的对象进行通信(意思是只支持客户端和服务器端都是Java程序的代码的远程调用)。 五、RMI的使用局限? 由于客户机和服务器都是使用Java编写的,二者平台兼容性的要求仅仅是双方都运行在版本兼容的Java虚拟机上。 六、RMI调用远程方法的参数和返回值 当调用远程对象上的方法时,客户机除了可以将原始类型的数据作为参数一外,还可以将对象作为参数来传递,与之相对应的是返回值,可以返回原始类型或对象,这些都是通过Java的对象序列化(serialization)技术来实现的。(换而言之:参数或者返回值如果是对象的话必须实现Serializable接口) 七、 RMI应用程序的基本模型 八、RMI体系结构
九、 RMI类和接口(完成一个简单RMI需要用到的类)。 (一) Remote接口:是一个不定义方法的标记接口 Public interface Remote{} 在RMI中,远程接口声明了可以从远程Java虚拟机中调用的方法集。远程接口满足下列要求: 1、远程接口必须直接或间接扩展Java.rmi.Remote接口,且必须声明为public,除非客户端于远程接口在同一包中 2、在远程接口中的方法在声明时,除了要抛出与应用程序有关的一场之外,还必须包括RemoteException(或它的超类,IOExcepion或Exception)异常 3、在远程方法声明中,作为参数或返回值声明的远程对象必须声明为远程接口,而非该接口的实现类。 (二) RemoteObject抽象类实现了Remote接口和序列化Serializable接口,它和它的子类提供RMI服务器函数。 (三) LocateRegistry final()类用于获得特定主机的引导远程对象注册服务器程序的引用(即创建stub),或者创建能在特定端口接收调用的远程对象注册服务程序。 服务器端:向其他客户机提供远程对象服务 SomeService servcie=……;//远程对象服务
客户机端:向服务器提供相应的服务请求。 Registry registry=LocateRegisty.getRegistry(); SomeService servcie=(SomeService)registry.lookup(“I serve”); Servcie.requestService(); (四) Naming类和Registry类类似。 客户端: Naming.lookup(String url) url 格式如下"rmi://localhost/"+远程对象引用 (五) RMISecurityManager类 在RMI引用程序中,如果没有设置安全管理器,则只能从本地类路径加载stub和类,这可以确保应用程序不受由远程方法调用所下载的代码侵害 在从远程主机下载代码之前必须执行以下代码来安装RMISecurityManager: System.setSecurityManager(new RMISecurityManager()); 十、demo开发 为了编写一个demo,我们分为两部分,一部分是server端的代码,一部分是client端的代码,client端的代码主要是为了使用server端的代码。当然这个代码是非常简单的,只是为了说明问题,现实的使用会使比较复杂的。 (一) 我们的目的 建立一个server端的java project,包含远程端的代码,定义接口,定义接口实现,然后在建立一个client端的java project,通过RMI使用远端服务中的方法。 (二) 我们的代码结构 (三) 远程服务代码 1. 远程服务的接口定义 第一步就是建立和编译服务接口的Java代码。这个接口定义了所有的提供远程服务的功能,下面是源程序: UserManagerInterface.java 1 package cn.com.tt.rmiserver.stub; 2 3 import java.rmi.Remote; 4 import java.rmi.RemoteException; 5 6 import cn.com.tt.rmiserver.bean.Account; 7 8 public interface UserManagerInterface extends Remote{ 9 public String getUserName() throws RemoteException; 10 public Account getAdminAccount() throws RemoteException; 11 } 接口必须继承Remote类,每一个定义地方法都要抛出RemoteException异常对象。 2. 接口的具体实现 第二步就是对于上面的接口进行实现: UserManagerImp.java 1 package cn.com.tt.rmiserver; 2 3 import java.rmi.RemoteException; 4 5 import cn.com.tt.rmiserver.stub.UserManagerInterface; 6 import cn.com.tt.rmiserver.bean.Account; 7 8 public class UserManagerImp implements UserManagerInterface { 9 public UserManagerImp() throws RemoteException { 10 11 } 12 private static final long serialVersionUID = -3111492742628447261L; 13 14 public String getUserName() throws RemoteException{ 15 return "TT"; 16 } 17 public Account getAdminAccount() throws RemoteException{ 18 Account account=new Account(); 19 account.setUsername("TT"); 20 account.setPassword("123456"); 21 return account; 22 } 23 } 3. 定义一个bean,实现implements Serializable序列化接口。也就是可以在client和server端进行传输的可序列化对象。 Account.java 1 package cn.com.tt.rmiserver.bean; 2 3 import java.io.Serializable; 4 5 public class Account implements Serializable,Cloneable{ 6 private static final long serialVersionUID = -1858518369668584532L; 7 private String username; 8 private String password; 9 10 public String getUsername() { 11 return username; 12 } 13 public void setUsername(String username) { 14 this.username = username; 15 } 16 public String getPassword() { 17 return password; 18 } 19 public void setPassword(String password) { 20 this.password = password; 21 } 22 } 4. 定义server端的主程序入口。 Entry.java 1 package cn.com.tt.rmiserver.entry; 2 3 import java.rmi.AlreadyBoundException; 4 import java.rmi.RemoteException; 5 import java.rmi.registry.LocateRegistry; 6 import java.rmi.registry.Registry; 7 import java.rmi.server.UnicastRemoteObject; 8 9 import cn.com.tt.rmiserver.UserManagerImp; 10 import cn.com.tt.rmiserver.stub.UserManagerInterface; 11 12 public class Entry { 13 public static void main(String []args) throws AlreadyBoundException, RemoteException{ 14 UserManagerImp userManager=new UserManagerImp(); 15 UserManagerInterface userManagerI=(UserManagerInterface)UnicastRemoteObject.exportObject(userManager,0); 16 // Bind the remote object's stub in the registry 17 Registry registry = LocateRegistry.createRegistry(2002); 18 19 registry.rebind("userManager", userManagerI); 20 System.out.println("server is ready"); 21 } 22 }[object Object](四) client端代码
3. 新建一个java Project,导入jar包,编写客户端代码。 4. 代码 ClientEntry.java 1 package weiblog.rmi; 2 3 import java.rmi.NotBoundException; 4 import java.rmi.RemoteException; 5 import java.rmi.registry.LocateRegistry; 6 import java.rmi.registry.Registry; 7 8 import cn.com.tt.rmiserver.stub.UserManagerInterface; 9 10 public class ClientEntry { 11 12 public static void main(String []args){ 13 14 try { 15 Registry registry = LocateRegistry.getRegistry("localhost",2004); 16 UserManagerInterface userManager = (UserManagerInterface)registry.lookup("userManager"); 17 System.out.println("用户名是"+userManager.getAdminAccount().getUsername() 18 +"密码"+userManager.getAdminAccount().getPassword()); 19 } catch (RemoteException e) { 20 // TODO Auto-generated catch block 21 e.printStackTrace(); 22 } catch (NotBoundException e) { 23 // TODO Auto-generated catch block 24 e.printStackTrace(); 25 } 26 27 } 28 29 }[object Object]5. 先运行服务器端代码, 然后运行客户端代码,就会显示运行结果,客户端可以运行多次,每次都可以取得服务器端的对象。如果要再次运行客户端代码就需要更改端口号,如果不更改就会显示端口号被占用。 |
|
来自: hewii > 《Software Tech.》