Apache Groovy Deserialization: A Cunning Exploit Chain to Bypass a PatchDecember 19, 2017 | Simon Zuckerbraun
This is the second in our series of Top 5 interesting cases from 2017. Each of these bugs has some element that sets them apart from the approximately 1,000 advisories released by the program this year. Today’s blog examines a remote code execution bug in Apache Groovy that bypasses a previous patch for a similar vulnerability.
In January 2017, the Zero Day Initiative (ZDI) published an advisory for Apache Groovy, ZDI-17-044/CVE-2016-6814. This vulnerability, reported to us in late 2016 by Sam Thomas of Secarma Limited, is a rather deft patch bypass for an earlier vulnerability that was also submitted via the ZDI program.
The original bug
The original bug, discovered by cpnrodzc7 and given the identifier ZDI-15-365/CVE-2015-3253, is a bug in the Groovy programming language. Groovy is implemented in Java and defines various new serializable Java classes. One of these classes,
MethodClosure, is particularly problematic from a security standpoint. A
MethodClosure instance holds a reference to an object and a method name. When a caller invokes
doCall on the
MethodClosure calls the named method on the specified object. By placing a serialized
MethodClosure into a stream, an attacker can cause quite a dangerous object to be formed upon deserialization. Furthermore, it is not difficult to include additional crafted objects in the stream so that the
doCall method will be invoked automatically, producing remote code execution.
It follows that it is quite hazardous for
MethodClosure to be a serializable class. Even if an application has no intention of passing serialized
MethodClosure instances, any attacker who is positioned to insert a serialized message can specify a
MethodClosure in the stream, which will be dutifully recreated into a live
MethodClosure object by the deserialization mechanism. Logically, the only remedy was to change the implementation of
MethodClosure so that it is no longer a serializable type. At this point, the Groovy developers were in a pickle. Changing
MethodClosure to be non-serializable would involve modifying the class hierarchy and could be too drastic a change for some existing code. They chose to take a middle path by defining a new static field,
MethodClosure.ALLOW_RESOLVE. Provided it is kept in its default state of false, any attempt to deserialize a
MethodClosure object will result in an exception throw:
You can view the change on GitHub here.
That ought to take care of the problem... right?
The researcher astutely recognized that this does not, in fact, solve the problem. The heart of the matter is this: even though an exception may be thrown during a deserialization operation, it’s not guaranteed that deserialization immediately terminates and all progress is discarded. Quite the contrary. The process of deserialization is performed in large part by code that is part of the classes being deserialized, and an attacker has wide latitude in choosing those classes. Some classes may even choose to completely ignore exceptions thrown while attempting to deserialize contained objects.
To defeat the patch, Thomas constructed the following gadget chain:
• The hazardous
MethodClosure is wrapped in a
KRBError. When the
MethodClosure throws an
catches it and instead throws an
KRBError is wrapped in a
BeanContextChildSupport contains code to handle an
IOException and does
not throw any further exception. Instead, it continues deserializing additional
• Further on within the same
BeanContextChildSupport, there is a serialized
AnnotationInvocationHandler. At deserialization time, this object invokes the
MethodClosure, resulting in remote code execution. Somewhat surprisingly, even
readResolve method shown above does not complete successfully, the
MethodClosure remains available to be referenced via the handle that
was assigned to it by the deserialization infrastructure.
The use of
AnnotationInvocationHandler to produce automated effects at deserialization time is already well known from the ysoserial Java deserialization payload project.
The serialized objects within the exploit may be visualized as follows:
In this way, the exploit chain bypasses the patch by continuing execution past the exception throw and achieves automatic code execution during deserialization.
Apache patches the patch
To defend against the new attack, the developers of Groovy patched
MethodClosure once more. This time, they introduced a custom
readObject method for
MethodClosure, so that by default
MethodClosure refuses to initialize its fields from a serialized stream:
You can view the change on GitHub here.
As before, an attacker might still be able to wrap the object and suppress the exception, thereby obtaining a deserialized
MethodClosure. However, the resulting
MethodClosure object is harmless because its fields have not been initialized to contain the attacker’s data.
Curiously, the way the
readObject method shown above is written, it throws an exception unconditionally. This breaks any legitimate uses of serialization of
MethodClosure. Yet the code has remained that way for almost a year. That suggests that there are in fact no legitimate uses for serialization of
The technique the researcher used for this patch bypass highlights the treacherous nature of deserialization vulnerabilities. The Java deserialization mechanism will create an object of any type that the input stream specifies, provided it is a serializable (or externalizable) type that can be found in the
CLASSPATH. Control flow during deserialization proceeds according to the code found within the specified type. This gives an attacker an unusual amount of freedom. Sometimes even an exception throw will be insufficient to stop an exploit chain from achieving remote code execution. When accepting data that may come from an untrusted source, it is recommended to completely avoid general-purpose deserialization mechanisms.