A full malicious exploit would use the protected ClassLoader.defineClass() method to load additional classes with arbitrary privileges that sets up a backdoor for hackers to infiltrate. The Metasploit exploit is one such malicious example. It is basically a trojan applet that runs in a web browser. When the browser downloads and initializes the applet embedded on a web page, the applet runs a shell process (cmd.exe for Windows and /bin/sh for Linux) and sets up a server so that a hacker could connect to the victim's machine to run arbitrary commands.
It is recommended that you first read this article on type confusion and this article on the actual exploit for some background information before you carry on reading.
There are 2 key points that make the malicious exploit possible:
- The AtomicReferenceArray class does not ensure type safety.
This class uses the sun.misc.Unsafe class to store references into its array field without ensuring type safety, thus allowing an instance of class A to pass off as an instance of class B. One of the objectives of the exploit is to use the JVM's ClassLoader instance defineClass() method to load additional classes with arbitrary privileges. Notice that the ClassLoader class is abstract, so you would need to get an instance of it indirectly, such as using ClassLoader.getSystemClassLoader() or Object.getClass().getClassLoader(). But since the ClassLoader.defineClass() method is protected, it is impossible to call the defineClass() method directly. It is also impossible to call the defineClass() method through a subclass of ClassLoader, that is, have class X call ClassLoader.defineClass() where X is a subclass of ClassLoader. To illustrate, consider the following code: This will not compile even though X is a subclass of ClassLoader, because X is outside of the package java.lang where ClassLoader is found. So accessing defineClass() is not as straightforward!
The proper way to call defineClass() is:
Notice the type of the argument 'inst' is changed to X. This code works because X inherits the protected defineClass() method from ClassLoader. However, any instance of X will not have sufficient privileges to call the inherited defineClass() method to load additional classes with arbitrary privileges. This is when type-confusion comes in. Because the AtomicReferenceArray class does not ensure type safety, we can use its set() method to pass off the JVM's ClassLoader instance as an instance of X. This sounds weird because ClassLoader is a superclass of X, and we are using ClassLoader as X! At line 9, the 'instance' argument is really an instance of ClassLoader and not X! At line 12, the Java runtime system is fooled into thinking we are calling X.getPackages() when we are really calling ClassLoader.getPackages().
- The Java object deserializer allows the use of references.
This is a crucial point that actually allows us to gain access to the private 'array' field of the AtomicReferenceArray class. TC_REFERENCE is used to expose this private field member. (See lines 103 to 116). Notice that the private 'array' field is of type Object but it is set to reference to an array of type X. This is perfectly legitimate as all classes in Java, except for Object itself, are subclasses of Object, either directly or indirectly. When the 'atomicRefArray' instance modifies its private 'array' field, it is actually modifying 'loaderArray'.
We handcrafted the 'data' array in the code below by modifying an existing serialized stream (which is based on Metasploit's exploit), to take advantage of the referencing ability. We commented the 'data' array so you can understand exactly what is going on. This exploit of the deserializer works in tandem with type-confusion to allow us to call the powerful JVM's ClassLoader instance's defineClass() method.
The code below is minimal to demonstrate the harmless exploit. Of course, the method convert() (line 19) is not necessary if the data array is stored as a byte array. But we made it an object array for the sake of clarity so that we can explain the actual contents.
The STREAM_*, TC_* and SC_* constants come from the ObjectStreamConstants interface which is implemented by ObjectInputStream, the Java default object deserializer. Strings are stored in the format defined by the method DataOutputStream.writeUTF().
Some questions you might have:
Some questions you might have:
- Why not just do a new X() to create a ClassLoader instance?
Answer: This would work perfectly fine if the MyExploit class is run by using, for example, "java MyExploit", otherwise this would generate a SecurityException if MyExploit is an applet. But then again if the code is run on the command line, it should already have all permissions, including access to the disk.
- Why can't I use sun.misc.Unsafe to directly bypass type safety?
Answer: The Unsafe class has a private constructor and cannot be subclassed. So the only way to get an instance of it is to use the static method Unsafe.getUnsafe(), but only trusted code has the rights to retrieve an instance (e.g. Java runtime libraries).
- How do I actually write a real exploit?
Answer: Well, one of the first steps is to turn MyExploit into a subclass of java.applet.Applet. And the rest, you have to figure it out .... :) But note that some browsers, such as Chrome, will prompt the user before running any applet. Also, there is already a patch released to fix this vulnerability but I think not all vulnerable machines have been patched yet...