/ SECURITY, ATTACH API, JMX, JCONSOLE

Beware the Attach API

A post brought to light an interesting feature of the JDK I didn’t know about: the ability to update a code running in a JVM. The referenced post shows how to apply a bugfix using that feature.

The devious white hat JVM hacker in me started to think how one could apply that trick for other less beneficial purposes. And of course, how to prevent that.

Hack overview

Let’s imagine a banking application based on the familiar Spring Boot stack. A TransferService class is dedicated to making transfer from one account to another. To keep things simple, the implementation is the following:

TransferService.kt
class TransferService {
  fun transfer(from: String, to: String, amount: Double) {
    println("Succesfully transferred $amount from $from to $to")
  }
}

The hack’s goal is to replace the previous implementation by this one:

TransferService.kt
class TransferService {
  fun transfer(from: String, to: String, amount: Double) {
    println("Succesfully transferred $amount from $from to me and $to got nothing")
  }
}

The "architecture" consists of two components beside the banking application itself:

  1. An agent to update the implementation
  2. A loader that will attach the agent to the running banking JVM
architecture

Their respective source code is pretty straightforward:

Attach.kt
fun main(args: Array<String>) {
  with(VirtualMachine.attach(args[0])) { (1)
    try {
      loadAgent(args[1])                 (2)
    } finally {
      detach()
    }
  }
}
1PID of the java process
2Path to the JAR agent

The VirtualMachine class is located in the com.sun.tools.attach package. On Java 8, the class is in tools.jar, thus requiring a JDK (instead of a JRE). From Java 9 onwards, it’s in the jdk.attach module.

HackAgent.kt
fun agentmain(args: String?, instrumentation: Instrumentation) {
  val clazz = Class.forName("ch.frankel.blog.attachapp.TransferService")
  val inputStream = Handle::class.java
                                 .classLoader
                                 .getResourceAsStream("TransferService.class") (1)
  val baos = ByteArrayOutputStream()
  inputStream.use { it.copyTo(outputStream) }
  instrumentation.redefineClasses(ClassDefinition(clazz, baos.toByteArray()))  (2)
}
1Load the hacked code that has been previously compiled
2Overwrite the original code with the hacked one

This is a pretty naive and straightforward implementation. A real implementations would probably try to be as unobstrusive as possible, e.g.:

  • keep the original code on disk, and re-inject it later
  • not wire all the funds to the hacker, but shave off a few cents on every transaction
  • etc.

Preventing the hack

The hack has two built-in limitations:

  1. The Attach API requires a PID. That implies the launcher must be run on the same machine.
  2. It seems the user trying to attach must be the same as the user having launched the target JVM.
    I could find the confirmation only in the IBM documentation

There are some additional ways to prevent the hack, or at least to drastically reduce the attack surface:

JVM flag

As its name implies, the -XX:+DisableAttachMechanism flag disables the attach feature. Setting the flag also prevents some useful tools, such as jcmd, jstack, jmap and jinfo.

Enabling the Security manager

The Security Manager with default permissions will prevent any attach attempts. The relevant permission is AttachPermission (with available names attachVirtualMachine and createAttachProvider).

I’ve been trying to raise awareness about the Security Manager in my talk "Securing the JVM, not for fun nor for profit, but what choice do you have?". I’d suggest you take 40 minutes to watch it to understand some of the consequences associated with running an unsecured JVM.
Custom AttachProvider

A viable option could be to develop a custom AttachProvider with built-in authentication. It requires knowledge and development time.

A note on Java 9 modules

I don’t have much experience with the Java 9 module system, but I don’t think it would offer any protection. Even then, not many organizations use it at the time of this writing.

Should you have any insight about it, please write it into the comments and I’d happy to update this section.

Conclusion

What’s sauce for the goose is also sauce for the gander: if a JVM is open for hot fixes, it’s also open to hacks.

Like a lot of technologies/approach in the industry, the Attach API carries some benefits. But be mindful: it’s not a magical solution! The benefit/risk ratio has to be evaluated in one’s own context.

I personally find that in most of cases, it’s not warranted. Moreover, posts mentioning this API should at the very least mention possible consequences, ones I highlighted above.

The complete source code for this post can be found on Github in Maven format.
To go further:
Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with narrower interests like Software Quality, Build Processes and Rich Internet Applications. Currently working for Exoscale. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More