More serialization hacks with AnnotationInvocationHandler
Nov 09, 2015
FoxGlove Security posted an interesting blog post this weekend about Java security vulnerabilities based on the Marshalling Pickles presentation from January. Exploits for these vulnerabilities are built on chains of gadgets.
In this post we take a closer look at two such gadgets: AnnotationInvocationHandler and BeanContextSupport.
An old AnnotationInvocationHandler bug
In 2011 I reported some similar serialization bugs in Spring, and for one of them I held back to details because Spring is fast to fix bugs, but Oracle is slow. It's four years later now, it's becoming relevant again so here are the details! The problem was AnnotationInvocationHandler; the same class being used now for the Apache Commons Collection problem. AnnotationInvocationHandler is the invocation handler for (implements the behaviour for) annotation objects in Java. It has a Class object representing the type of the annotation, and a map from properties to values. When you call a method on the annotation interface, it will return the corresponding value from the map.
Back in 2011, the problem was that deserialization didn't check if its type was really an annotation. So we could create an AnnotationInvocationHandler for any interface, and it would handle the method calls as if it was an annotation, giving our pre-canned return values. For Spring, an interesting interface to apply that to was Authentication, because we could make it ignore setAuthenticated calls and make isAuthenticated always return true. Note that Spring was just one example that happened to fit this old bug, I'm sure there were more.
Is AnnotationInvocationHandler really fixed?
But that was then and this is now. AnnotationInvocationHandler.readObject now throws InvalidObjectException when the type is not an annotation, so that shouldn't work anymore:
Oh, but look, it first reads the properties, and only then throws. So we do actually deserialize a bad instance and only afterwards throw it away. I didn't look deeper into it, but from the "Marshalling Pickles" slides I see that also involves an AnnotationInvocationHandler that is still being deserialized (so before it reached the check). In their case it is for an actual annotation though, so there that might not really matter. It does demonstrate the point that throwing an exception after calling defaultReadObject is not going to prevent all abuse.
That only works if we can trigger our exploit during the deserialization itself; if we don't need the application to actually use our object. Because before serialization finishes, it's still going to reach the point where it throws that exception, practically killing the chances of abusing the object further.
What if we could catch that exception and then go on deserializing the rest of the stream which might again contain a backreference to that bad object? We want to do something like this:
We're just sending serialized data, so someone would have to do that for us. After a few minutes of grepping JDK sources, this gadget in java.beans.beancontext.BeanContextSupport popped up, looks like a good fit:
Demo
Let's see how that works with a toy example. Suppose our application accepts some sort of security tokens that look like this:
Our goal is to create a MySecurityToken annotation (although that's not an actual annotation interface) by deserializing an AnnotationInvocationHandler, catching the exception it throws with BeanContextSupport and recover the handler again. Although the code in the application suggests the areYouReallyWhoYouSayYouAre method always returns false; our deserialized instance will return true. Note that MySecurityToken is not even serializable; that does not stop us.
To write the stream we'll use a slightly adapted version of Converter from Sami's Breaking Defensive Serialization post. There's a few small tricks to make this actually work, see the comments in the code.
Not-so-arbitrary code execution
AnnotationInvocationHandler.equalsImpl is also... special. When we call equals on an annotation and give it an object implementing the same interface but not using AnnotationInvocationHandler, then it goes through all the methods on the interface and calls them on that object. Since I originally reported this it has been tightened down: before calling any method it checks if all the methods look like they could be from an annotation (public abstract without arguments, a return type supported by annotations,...). I don't see a way to directly exploit it, but it does still make a curious gadget.
For example, say there is a serializable IntSupplier on our classpath. We can put it in a stream together with a fake IntSupplier annotation, and if we can get something to call equals on it (e.g. when putting them in the same HashMap with the same hashcode) it will invoke the IntSupplier.getAsInt method.
Can you find some more interesting uses of this?
Conclusion
These kind of issues aren't new. AnnotationInvocationHandler or BeanContextSupport are not the real problem. If you've been following the Java security news, my main message should sound pretty familiar: Don't deserialize untrusted data.