On every patch cycle, someone somewhere freaks out. It's life. After so many years of working in exploit development everyone learns to make peace with that fact (well, maybe not Max).
This time Esteban had valid reasons. As it turns out Java decided to burn all their bridges and basically set their default security level to High. Which means that every-time a website serves an applet you get an awful warning message that requires a user's confirmation.
This pretty much kills all your Java bugs... Why? Because if your attack strategy relies on a user clicking through a prompt then it's just better to self-sign an applet and it will run out of the sandbox when they click ok. Simple right?
But then, we are talking about Java, right? So we decided to take a look at the implementation to see if there was any way we could bypass it.
Fifteen minutes later Esteban came back to my desk. The job was done. The high fives were his.
Bug? Feature? Miscarriage?
It hard to classify this vulnerability especially since this was supposed to be the main security implementation that Java released and something that people were blogging about.The function responsible for initializing the Java applet Plugin2Manager.initAppletAdapter, is the one that contains the new security protections.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | void initAppletAdapter(AppletExecutionRunnable paramAppletExecutionRunnable) throws ClassNotFoundException, IllegalAccessException, ExitException, JRESelectException, IOException, InstantiationException { long l = DeployPerfUtil .put(0L, "Plugin2Manager.createApplet() - BEGIN"); String str1 = getSerializedObject(); String str2 = getCode(); Plugin2ClassLoader localPlugin2ClassLoader = getAppletClassLoader(); DeployPerfUtil .put("Plugin2Manager.createApplet() - post getAppletClassLoader()"); Object localObject1; if (_INJECT_EXCEPTION_CREATEAPPLET) { localObject1 = new IOException( "INJECT_PLUGIN2MANAGER_EXCEPTION_CREATEAPPLET"); throw ((Throwable) localObject1); } if (_INJECT_CREATEAPPLET_NULL) { System.out.println("INJECT_PLUGIN2MANAGER_CREATEAPPLET_NULL"); return; } if ((str2 != null) && (str1 != null)) { System.err.println(amh.getMessage("runloader.err")); throw new InstantiationException( "Either \"code\" or \"object\" should be specified, but not both."); } if ((str2 == null) && (str1 == null)) return; if (str2 != null) { localObject1 = localPlugin2ClassLoader.loadCode(str2); DeployPerfUtil .put("Plugin2Manager.createApplet() - post loader.loadCode()"); String str3 = getAppletCode(); Object localObject2 = null; if (!str3.equals(str2)) localObject2 = this.loader.loadCode(str3); else localObject2 = localObject1; this._signedApplet = isAppletSigned((Class) localObject2); if (localObject1 != null) if (fireAppletSSVValidation()) { appletSSVRelaunch(); } else { checkRunningJVMToolkitSatisfying(); checkRunningJVMArgsSatisfying(); if (paramAppletExecutionRunnable != null) paramAppletExecutionRunnable .sendAndWaitForAppletConstructEvent(); this.adapter.instantiateApplet((Class) localObject1); DeployPerfUtil .put("Plugin2Manager.createApplet() - created applet instance"); } } else { if (!this.isSecureVM) return; this.adapter.instantiateSerialApplet(localPlugin2ClassLoader, str1); this.doInit = false; DeployPerfUtil .put("Plugin2Manager.createApplet() - post: secureVM .. serialized .. "); } if (!this.adapter.isInstantiated()) { System.out.println("Failed to instantiate applet??"); return; } if (this.shouldStop) { setErrorOccurred("death"); this.adapter.abort(); if (DEBUG) Trace.println("Applet ID " + this.appletID + " killed during creation", TraceLevel.BASIC); logAppletStatus("death"); synchronized (this.stopLock) { this.stopSuccessful = true; this.stopLock.notifyAll(); } return; } DeployPerfUtil.put(l, "Plugin2Manager.initAppletAdapter() - END"); } |
I will give you 5 second to read the code and facepalm.
5... 4... 3... 2... 1... boom!
At the beginning of the function, the code obtains a serialized object (str1 variable) and the code (str2 variable) and only one of them can be used (line 7 and 8).
The normal way people use applets is through the “code” attribute (str2 variable), and as a result the fireAppletSSVValidation method (line 41) is called thus displaying the warning to the end user. In order to bypass this protection, the exploit has to take the second route and load the applet through a serialized object (line 56).
1 | <embed object="object.ser" type="application/x-java-applet;version=1.6"> |
Oracle's fix was as one-liner, they added a call to fireAppletSSVValidation() before line 56. Job Done.
Is Java dead now? I will leave that as an exercise to you, and if you don't know how to approach this, maybe you are interested in learning how to audit and find your own bypass? In that case we can discuss it in April during Master Class Java Auditing Extravaganza.
Since Immunity is very pleased with the revamped blog page, we decided to share this happiness by offering discounts for INFILTRATE 2013. Send an email to infiltrate@immunityinc.com with the subject "KEEP CALM and Run this Applet" and you will receive a 10% discount off of the conference briefings pass, Unethical Hacking training and/or Web Hacking training. This is a limited time promotion. You have until Friday February 8, 2013 @ 4pm EST to take advantage of these savings!
Hello Esteban,
ReplyDeleteI was wondering how you serialize your object.ser. Because I tried to serialize my applet class with this code :
FileOutputStream fout = new FileOutputStream("applet.ser");
ObjectOutputStream oos = new ObjectOutputStream(fout);
AppletCustom rp = new AppletCustom();
oos.writeObject(rp);
oos.close();
Is it a basic java object serialization ?
Thank you and really nice work ;)