Tuesday 14 October 2014

A Tale of Two .NET Methods

Sometimes the simplest things amuse me. Take for example CVE-2014-0257 which was a bug in the way DCOM was implemented in .NET which enabled an Internet Explorer sandbox escape. Via the DCOM interface you could call the System.Object.GetType method then command the reflection APIs to do anything you like, such as popping the calculator. The COM interface, _Object, which exposed the GetType method only has 4 functions on it, it seemed pretty unlucky that 25% of the interface had a security vulnerability. Still Microsoft fixed this bug and all's well with the world. Then again if you were lucky enough to see any of my IE11 sandbox presentations you might have seen the following slide, although briefly:

Why would I point out the Equals method as well? Well because it also has a bug, one so difficult to fix that basically Microsoft has throw up its hands and given up on Managed DCOM. They've mitigated the issue in the OneClick deployment service (CVE-2014-4073) by reimplementing the DCOM object in native code, but as far as I'm aware they've not fixed the underlying issue.

To understand the problem we have to go back to CVE-2014-0257 and understand why it worked. When the GetType method returns a System.Type instance over DCOM it wraps the object in a COM Callable Wrapper (CCW). This looks to the COM infrastructure as a normal pass-by-reference object so the Type instance stays in the original process (say the ClickOnce service) but exposes the remote COM interfaces to the caller. The Type class is marked as Serializable so why doesn't the CCW implement IMarshal and custom-marshal the object to the caller? It would be a pretty rude thing to do, It would force the CLR to be loaded into a process just because it happened to be communicating with a .NET DCOM server.

If you implement similar code in C# though things change. The result of calling GetType is a local instance of the Type class. How does .NET know how to do this? This is where the IManagedObject interface gets involved. Every CCW implements the IManagedObject interface which has two methods, GetObjectIdentity which is used to determine if the object exists in the same AppDomain and GetSerializedBuffer which, well, I guess the name describes itself.

When a .NET client receives a COM object it tries to see if it's really a .NET object in disguise. To do this it calls QueryInterface for IManagedObject, if that succeed it will then call GetObjectIdentity to see if it's already in the same AppDomain (if so it can just call it directly). Finally it will call GetSerializedBuffer, if the wrapped .NET object is Serializable it will receive a serialized version of the object which it can recreate using the BinaryFormatter class. Yes that BinaryFormatter class.

Oh crap!

This of course works in reverse, if a COM client passes a .NET object to a DCOM server it can cause arbitrary BinaryFormatter deserialization in the server. The Equals method will accept any object by design. So by passing a malicious serializable .NET object to Equals you can end up doing fun things like reflecting an arbitrary Delegate over the DCOM interface. As you can imagine that's bad.

At this point I'll direct you the exploit code, which should make everything clearer, or not. There's actually a lot more to the exploit than it seems :-)