Java RMI for pentesters: structure, recon and communication (non-JMX Registries).

The purpose of this article is to explain to you, what are RMI interfaces you might encounter during penetration testing of infrastructure. Since the whole topic I’d like to cover is a bit long, I’ve split it into two parts. In the following part, I’ll just briefly explain what RMI interfaces are, how to create one for testing purposes and also how to build a RMI Client manually to invoke remote methods. The attack part will be described in the second part of this post, which can be found here.

Also, these articles are about native RMI registries. There are also popular JMXRMI registries which are somewhat different. I am planning to release a separate article about JMX which will cover JMXRMI among other ways to interact with Java Management eXtension. So in short, what I’ll describe here is:

  • What are RMI Interfaces
  • How to build an RMI Interface from source (code included)
  • What information about an RMI Interface can be obtained using Nmap scan
  • How to build an RMI Client (and what you need to know to build one)
  • What are typical issues / stack traces when dealing with RMI’s and what might be its reason

What is Java RMI

Java RMI server is a virtual entity exposed over the network that allows other remote parties (clients) to execute methods on a system (technically a JVM running on that system) on which it is running. It’s nothing exceptional in the programming world — where similar concepts like Remote Procedure Call (RPC) are widely used.

Thus, by running an exposed RMI Server on a system, one can allow external actors to interact with it and possibly execute methods on the RMI Server. These methods should be defined within the Server implementation. Once they are called by a client, they will be executed on the Server and the return values will be returned to the client. Another interesting part is that native RMI (again, I am NOT talking about JMXRMI) does not support much of security apart from encrypting the connection using SSL. [1]

RMI interface’s architecture is presented below:

https://afine.com/wp-content/uploads/2023/07/remote-method-invocation.jpg

The names “stub” and “skeleton” might be confusing at the first sight, but it’s simply how the “client” and “server” part of the remote object is called.

Stub is a class that implements the remote interface and serves as a client-side placeholder for the remote object. On the other hand, Skeleton is a server-side entity that dispatches calls to the actual implementation of the remote object.

The RMI registry itself is a Java utility, which can be found in JDK’s binaries under name “rmiregistry”. Launching that binary with a numeric argument which is port to listen on (default 1099), will allow to Bind the remote objects to it. These remote objects can be accessed later from the network outside the machine on which rmi registry is running.

In simple words: we first start the RMI Registry, and then create a Java Object (Java class which has some methods to be called by remote parties) and give it a Name (Binding) under which it can be found within the registry.

In order to allow remote parties to execute some methods, RMI registry has to consist of at least two programmistic components:

  • An Interface, which defines what methods will be invokable on the remote object
  • The code to bind and export the remote objects (remote methods will be invoked on that object)
  • Implementation of the remote methods

Building RMI Client & Server

Let’s use the simple example of an RMI Server and Client. First, there will be two standalone classes for server logic. You can also find full code for that article on my GitHub repository here.

//RMIInterface.java
import java.rmi.*;
import java.rmi.registry.*;
import java.net.*;
interface RMIInterface extends Remote {
  public String echo(Object obj) throws RemoteException;
}
//Server.java
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.Naming;
public class Server extends UnicastRemoteObject implements RMIInterface {
  public String echo(Object input) throws RemoteException {
    return“ Echoing: “+input.toString();
  }
  protected Server() throws RemoteException {
    super();
  }
  public static void main(String[] args) {
    try {
      System.out.println(“[+] Trying to bind…”);
      //Below IP:PORT can be changed
      Naming.rebind(“rmi: //127.0.0.1:11099/RMIInterface”, new Server());
        System.out.println(“[+] Server started.”);
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

These two files should be stored in the same folder. Now, compile them and run rmiregistry. Note, that you should run RMIRegistry from the same directory in which these files reside (so your current working directory should be unchanged and then you run rmiregistry binary)

Reconnaissance of RMI Interface using Nmap

At this point, we can use nmap to scan the localhost interface where the rmi registry and all its bindings are visible:

nmap -sV -p 11099 -T4 -A localhost

Above listing is the result of nmap’s script named rmi-dumpregistry. It tells us, that:

  • The registry runs on port 11099
  • It makes use of (implements) RMIInterface, which is a custom class and we do not know it’s structure in pure blackbox perspective (however how we know it as we created it)
  • The invocationHandler runs on port 34087. Invocationhandler is in short the endpoint which takes care of execution of remotely invoked methods.

Below image shows the order in which interaction with an RMI registry goes:

Creating an RMI Client

For a complete picture, let’s implement the client code. Client.java is placed in the same directory as Server and RMIInterface.

//Client.java
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class Client implements RMIInterface {
  public String echo(Object input) throws RemoteException {
    return“ Echoing: “+input.toString();
  }
  private static RMIInterface look_up;
  public static void main(String[] args)
  throws MalformedURLException, RemoteException, NotBoundException {
    look_up = (RMIInterface) Naming.lookup(“rmi: //127.0.0.1:11099/RMIInterface”);
      System.out.println(“Calling Echo…“);
      try {
        String response = look_up.echo(“Let’ s use a string here.“);
        System.out.println(response);
      } catch (Exception e) {}
    }
  }

Note, that Client.java implements the interface which was used on Server side, and in current case we have provided the same implementation of function echo as it is present on server side.

After compilation you can see that the client can be called. If you take a look at wireshark while running the Client, you can confirm the above graphic flow, as the first connection to RMI Registry is observed and after it there is a second connection done to the InvocationHandler.

In the beginning, the client is talking just to the Registry port, 11099

Later, it is being instructed to talk again to the InvocationHandler which will service the method execution:

You can observe the echo method’s result being returned from that endpoint.

Also, it is worth mentioning that we can see the data being exchanged in serialized form, which can be said by looking at serialized “magic bytes” 0xaced0005. This will be further discussed in second part of the article about attacking RMI registries (Spoiler: modern Java versions already mitigate such attacks 🙁 )

So basically, in order to connect and execute method on the remote server, we needed:

  • The code of the interface server is using — which is not publicly visible or downloadable from the registry
  • The binding name and registry port which was detected by nmap
  • The invocation handler that has to be accessible on network level under the IP/hostname it is presented in registry dump

Troubleshooting the Client

Below we’ll discuss two common obstacles that might be encountered at this point: Foreign InvocationHandler and lack of server-side Interface.

As shown in the wireshark dump above, during connection to a RMI Registry, first the registry will be hit and next the client will be redirected to the respective InvocationHandler. If the InvocationHandler is set to foreign host e.g. localhost or an unresolvable hostname, remember that it is still your machine that will try to connect to the address of InvocationHandler.

  • If it is set to localhost, you will get a connection error, because after connecting to the registry, your client will try to connect to InvocationHandler on the localhost of your machine. You have to set up a relay using e.g. firewall or tool like socat and redirect traffic from [localhost]:[port of invocation handler] to [rmi_host]: [port of invocation handler]
  • If it is set to an unresolvable hostname, try adding the rmi server’s ip address together with the foreign hostname to /etc/hosts file, so it is properly resolved and you will be able to connect to it

The second obstacle on your way to executing remote methods is the code of the Interface. If you do not have the server-side interface, things are more complicated.

What if the target Interface is unknown?

In the second part of the article, which will be released soon, I will show you RMIScout tool that allows to automate the process, however, so far let’s take the manual approach (I believe manual approach should always come first for understanding what happens behind the scenes; only then automated approach can be used effectively)

Consider the following example: We will start the rmiserver provided above and we will copy the client class and the interface class to different folder.

In that case, if you are able to guess the method name and arguments, you will be able to invoke it. Consider the following client code:

//Client2.java
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class Client2 implements RMIInterface {
  public String echo(Object something) throws RemoteException {
    String notUsed = something.toString();
    return“ Sorry, I don’ t know the original implementation”;
  }
  private static RMIInterface look_up;
  public static void main(String[] args)
  throws MalformedURLException, RemoteException, NotBoundException {
    look_up = (RMIInterface) Naming.lookup(“rmi: //127.0.0.1:11099/RMIInterface”);
      System.out.println(“Calling Echo…“);
      try {
        String response = look_up.echo(“Let’ s use a string here.“);
        System.out.println(response);
      } catch (Exception e) {}
    }
  }

If you run such Client code, the code will be run and the Server implementation will be executed. That means, to execute remote methods you just need to know the Interface name along with methods names, returned data types and arguments data types. Why is it important? Simply, if you are able to obtain an information about at least one function that is present in the Server side, you can construct a dummy interface of the same name as dumped by nmap, place there proper function signature (returned data type, name and argument types — only these) and create a client that implements that interface with a dummy implementation of that function.

It is only important that the interface is present on the classpath of the client, so it might be needed to construct a dummy package like org.company.rmipackage.RMIInterface, put it in a dummy .jar file and in the end put in on classpath. Once you call it, you will be able to execute the server-side method and get it’s proper result (if it returns any result value of course).

This, in turn, shows that you just need to guess the method signatures (return data type, method name and arguments data type) in order to be capable of executing them. If you are lucky enough, you might be able to find the complete RMI Interface on GitHub, which in turn might allow you also to examine what a certain method does. Otherwise, RMIScout that will be discussed in the second part of this article, will be a great choice to discover potential methods on the server side. If you are able to identify RMI methods, some of them might already help you to get a foothold on the target system.

You can find the full code of the programs used here on GitHub here.

Troubleshooting RMI Registries

When working with RMI registries described above, or with any other tool that connects to a remote registry (also ysoserial or rmiscout and so on) you might encounter an error which is often accompanied by a stack trace. While stack traces are great help for what went wrong, it is perfectly understandable that you might not want to dive into troubleshooting and just get that tool working and move on. Below list shows common exceptions that are encountered during dealing with RMI registry stuff with short explanation and remediation. Note that each case is different so they will not work in 100% cases, but if you feel stuck, these can be your first steps.

Client-side exceptions:

  • java.security.AccessControlException: access denied (java.net.SocketPermission hostname.server.com)

JVM has no permissions to open remote socket. This might be an issue with your java security policy (java.policy).

  • exception: Connection refused to host: 10.10.4.1; nested exception is:
     java.net.ConnectException: Connection refused

The connection cannot be established. This is because the registry is not reachable on network level. The target registry might have been turned off, or something is blocking network connection.

  • java.rmi.NotBoundException:

This means that registry exist but binding you are looking for does not exist. For example, you wanted to bind to “MYRegistry” but you made a typo and wrote “MYRegistr”, so you got NotBoundException

  • exception: error unmarshalling return; nested exception is:
     java.lang.ClassNotFoundException: [ClassName]

This means that you are missing the [ClassName] on your Classpath. Try running your rmi client (or tool) from the directory where your Interface is present. If you are running a jar tool, it might be needed to unpack it, add a compiled class to a certain package inside and re-pack the jar. Check out following link for some explanation about how packages work if you are not sure how to do it: https://www.geeksforgeeks.org/packages-in-java/

Exceptions when running the server:

  • java.rmi.server.ExportException: Port already in use: 1099; nested exception is:

This may happen when you try to run rmiregistry twice on the same port.

  • java.rmi.AccessException: Registry.Registry.rebind disallowed; origin foreign.host.com is non-local host

RMI can only be bound to localhost — if you get this error, you probably tried to bind to a registry which is residing on a remote machine.

  • java.rmi.AlreadyBoundException: MyRegistry

That simply means that such binding is already in place. Change the name or unbind the previous object.

That’s pretty much all that is needed to know about the basics of RMI Registries. It’s pretty long but if you want to use automated attack tools in a smart way and be more effective when testing infrastructure, it’s worth exercising. In the next part I will show you the juicy part — the enumeration and attack techniques which might differ depending on target’s Java security level and patching policy.

Author: Lukasz Mikula — penetration tester & head of R&D @ AFINE. Follow me on linkedin (https://www.linkedin.com/in/lukaszmikula/) or twitter (https://twitter.com/0xluk3)

References:

[1] https://www.slideshare.net/NickBloor3/nicky-bloor-barmie-poking-javas-back-door-44con-2017

Also, the text was inspired by reading the following articles (and I recommend you read them too!):

Is your company secure online?

Join our list of satisfied customers and safeguard your company’s data!

Trust us and leave your contact details. Our team will contact you to discuss the details and prepare a tailor-made offer for you. Full discretion and confidentiality of your data are guaranteed.