Introduction

Welcome to Part 2 of my previous post on loading a Java class in Tomcat. A possible way to stop this kind of attack would be to dynamically instrument the JVM to ensure that unknown classes are either prevented from loading or flagged to the SOC team. This mechanism is also known as Runtime Application Self Protection or RASP.

What is RASP?

RASP hooks into an application and actively monitors the program flow during runtime. In some setups, it can actively monitor user input and detect potentially bad input, for our purposes, it can be used to alert when an unknown Java class is loaded or a sensitive function is being executed.

Sadly since I never got to test a commercial RASP during my PSA days, so I can’t say much about how effective those products are in practice personally, but for my purposes, a custom RASP can solve the issue.

Coming back to the problem at hand, we can create a simple proof of concept RASP that just monitors loaded classes that could be used to alert defenders if an unusual class is loaded by the server. The premise is that production servers generally only load a fixed set of classes throughout their operation (especially for microservices these days). Therefore we could determine this fixed set after observing the runtime operation for a while and then alert if there are any unusual classes being executed.

Our simple proof of concept RASP

The JVM has a robust Instrumentation API to allow developers to develop probes that can hook into the JVM.

For our proof of concept, we can develop the RASP such that it will hook into the JVM and periodically monitor the loaded class list. If an attacker were to load his implant into Tomcat, this monitor should be able to flag it out when it loads.

Code

Since I’m a script kiddie, I followed largely the excellent guide by Baeldung on creating such a probe.

My simple program has 2 classes, Agent and Probe.

Agent is the class that would search for Tomcat’s JVM and inject Probe into it.

public class Agent
{
    public static void main(String[] args) throws Throwable
    {
        String applicationName = "catalina";

        Optional<String> jvmProcessOpt = Optional.ofNullable(VirtualMachine.list()
                                                                           .stream()
                                                                           .filter(jvm -> {
                                                                               System.out.println("jvm: " +
                                                                                                  jvm.displayName());
                                                                               return jvm.displayName().contains(
                                                                                       applicationName);
                                                                           })
                                                                           .findFirst().get().id());
        if (jvmProcessOpt.isEmpty())
        {
            System.err.println("Target Application not found");
            return;
        }
        String jvmVid = jvmProcessOpt.get();

        VirtualMachine virtualMachine = VirtualMachine.attach(jvmVid);

        String jarPath = "output.jar";
        File   jarFile = new File(jarPath);
        virtualMachine.loadAgent(jarFile.getAbsolutePath());
        virtualMachine.detach();

    }
}

In order to function properly as a probe, the Probe class needs to implement the function premain or agentmain.

premain is the function that will be called if the probe is attached statically at startup, while agentmain is executed when the probe is attached into a running JVM. This is also explained in the JDK docs.

The code snippet below shows my probe instantiating agentmain and creating a new Probe object. The Probe object will then be started. It’s a simple class that will create a ScheduledExecutorService which will call listClasses() every 10 seconds.

    public static void agentmain(String agentArgs, Instrumentation inst) throws Exception
    {
        System.out.println("com.potato.Agent Main");

        Probe probe = new Probe(inst);
        probe.start();
    }

    public void start()
    {
        System.out.println("com.potato.Probe starting");

        try (ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor())
        {
            exec.scheduleAtFixedRate(() -> listClasses(inst), 0, 10, TimeUnit.SECONDS);

            while (true)
            {
                Thread.sleep(1000);
            }
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }

Catching our implant

You can either inject the probe on JVM startup or dynamically hook in when the JVM is running. Since I am lazy, I just injected the probe that was written in the previous section via the command:

java -classpath /usr/local/java/lib/tools.jar:output.jar com.potato.Agent

Note that I had to include tools.jar in the classpath because I was using sun packages, which are technically internal packages supported only by Oracle, so these classes aren’t usually on the main classpath.

As the program runs in a loop (ScheduledExecutor!), it will print out non whitelisted classes every 10 seconds.

As a quick recap, the RMI payload from my previous post initialised the Nashorn JavaScript engine and used it to fetch a Java class from a remote source to be instantiated.

The following screenshot is what my Probe class prints after I send in my RMI payload.

In the printouts above, we can see that a strange class called templates.ExecFetchEnvironmentJDK8 is being loaded into memory. The code source is also from a remote path *gasp*. Totally suspicious and should warrant further investigation.

Also note that there’s a reference to Nashorn libraries. This is because my RMI payload also functions as a watchdog to keep my Java class up to date and alive. The presence of Nashorn running could also be an IOC, because it’s most likely a feature that most production servers will not use.

Conclusion

This was a simplistic example of using Java Instrumentation classes to track loaded classes in the JVM.

Other than alerting on suspicious loaded classes, we could also extend the probe to alert on certain functions that are called. An example of such functions could be URLClassLoader.addURL, Runtime.exec, and also the Nashorn classes. Also, other than alerting on such function calls, there’s also a potential for preventative actions by NOOP-ing such functions to flummox poor attackers. This can be done using the same Instrumentation API provided in the JDK (ClassFileTransformer looks useful).

While all this sounds cool, there are a lot of hurdles to implementing this in practice. It’s difficult to have a one size fits all approach as different production servers run all sorts of different code. I mentioned Runtime.exec previously, and while it can be a rare function in some servers, it could be a legitimate call for a media processing server. This means that the system owners would need to sit down and properly profile the server in order to write meaningful rules to catch such an attack.

Another consideration is the potential performance impact. This approach could be fairly invasive and performance sensitive system owners might balk at integrating such a system.