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.
This is the 5th post in the JVM Security focus series.Other posts include:
- The Java Security Manager: why and how?
- Proposal for a Java policy files crafting process
- Signing and verifying a standalone JAR
- Crafting Java policy files, a practical guide
- Beware the Attach API (this post)
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:
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:
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:
- An agent to update the implementation
- A loader that will attach the agent to the running banking JVM
Their respective source code is pretty straightforward:
fun main(args: Array<String>) {
with(VirtualMachine.attach(args[0])) { (1)
try {
loadAgent(args[1]) (2)
} finally {
detach()
}
}
}
1 | PID of the java process |
2 | Path 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.
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)
}
1 | Load the hacked code that has been previously compiled |
2 | Overwrite 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:
- The Attach API requires a PID. That implies the launcher must be run on the same machine.
- 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 asjcmd
,jstack
,jmap
andjinfo
. - Enabling the Security manager
-
The Security Manager with default permissions will prevent any attach attempts. The relevant permission is
AttachPermission
(with available namesattachVirtualMachine
andcreateAttachProvider
).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.