When Java throws you a Lemon, make Limenade: Sandbox escape by type confusion
April 25, 2018 | Vincent LeeLast week, Oracle released their quarterly Critical Patch Update (CPU). Seven of these bugs were submitted through the Zero Day Initiative (ZDI) program, and one of these bugs was quite reminiscent of the Java submissions in late 2012 and early 2013. The bug, CVE-2018-2826 (ZDI-18-307), is a sandbox escape vulnerability due to insufficient type checking discovered by XOR19. An attacker with low execution privileges may exploit this vulnerability to bypass the SecurityManager
and escalate privileges.
The Vulnerability
The vulnerability lies in the implementation of reflection API, java.lang.invoke.MethodHandles::tryFinally(MethodHandle target, MethodHandle cleanup)
method. I’ll refer to this API as MethodHandles::tryFinally()
hereafter. This API returns a MethodHandle
that adapts a target
method handle by wrapping it in a try-finally
block. The cleanup
method handle represents the functionality of the finally block. Any exception thrown during the execution of the target
handle will be passed to the cleanup
handle.
In the implementation of MethodHandles::tryFinally()
, insufficient type checks were performed. It is possible to for an attacker to assign mismatched Throwable
objects between target
and cleanup
MethodHandlers, resulting in a type confusion condition. The attacker in turn is able to cast any object into arbitrary types.
An Example
Suppose we have the following two classes and methods.
The attacker constructs a MethodHandle with throwEx()
and handleEx()
as cleanup
. Notice that throwEx
throws a Cast1
-typed Throwable
and handleEx()
handles the Cast2
-typed Throwable
. When the attacker invokes the newly constructed MethodHandle in a vulnerable version of Java, it passes the Cast1
Throwable
object into handleEx()
, which handles Cast2
-typed Throwable
object without complaint or proper type checking. As handleEx()
proceeds to process the Throwable
, it handles the Lemon
as a Lime
, which allows the attacker to cast the Lemon
into Lime
and makeLimenade()
out of Lemon
.
The Exploit
In the exploit, the attacker attempts to bypass the Security Manager
by setting it to null
via reflection. First, the attacker obtains a methodHandle
to a setter method on the
security
field of the System
class via MethodHandles::publicLookup()
. However, the security
field is private and is thus normally inaccessible to MethodHandles::publicLookup()
, so the attacker defines the following code and casts the Lookup
object into a dummy LookupMirror
object with the type confusion vulnerability:
In handleEx()
, the attacker manipulates the Lookup
object as LookupMirror
object. From the relevant OpenJDK source code below, the attacker has changed the allowedModes
property of the Lookup
object into TRUSTED
, allowing the attacker to obtain MethodHandles
to arbitrary fields, including System.security
, the SecurityManager field.
Finally, the attacker then obtains a method handle to the setter method of the System.security
field using the trusted Lookup
object and sets SecurityManager
to null
in order to bypass all restrictions imposed by a SecurityManger
.
The Patch
Oracle patched this vulnerability by converting the object thrown by the target
method handle into one that cleanup
can handle. This ensures the MethodHandles::tryFinally()
API will throw a WrongMethodTypeException
when the types are mismatched and incompatible for explicit casting.
Conclusion
It’s interesting to see these Java type confusion bugs continue to be submitted to the program even though they aren’t used by exploits as much these days. Once browsers implemented “Click-to-Play,” practical exploitation became more difficult. This is also reflected in the amount of Oracle Java bugs submitted to the program over the years. That steep drop off coincides with the adoption of Click-to-Play and also illustrates how implementing mitigations to take out classes of vulnerabilities shifts researchers to other targets.
When my colleagues presented on Java exploits back in 2013, Oracle claimed more than 3 billion devices ran Java and that number is likely even higher now. Type confusion bugs were the most exploited vulnerability type back then, so it’s good to see these patched – even if they are never targeted.
You can find me on Twitter @TrendyTofu, and follow the team for the latest in exploit techniques and security patches.
Disclosure Timeline
· 2017-12-19 - Vulnerability reported to vendor
· 2018-04-18 - Coordinated public release of advisory