0

I have a legacy Windows application that needs to be looked over in terms of security. During this review something caught me eye. In the out of process COM server I'm looking at is a method that accepts an arbitrary class pointer. The method manually queries the attacker supplied interface for a specific IID and proceeds to call methods on that interface. It's possible for an attacker to supply any COM class pointer they want to this method, and no further verification is done. The COM server is running the process of a more privileged user.

I don't know much about COM, but this behavior feels unsafe to me. Can an attacker exploit this behavior by sending their own specially crafted COM object or is this safe?

I've been struggling reading documentation and experimenting. I can't seem to find a definitive answer to this question anywhere on MSDN or the web. Could be that I just don't understand the technology enough.

EDIT:

The server's method looks like this, I've abridged it slightly:

HRESULT __stdcall CCallbackRegister::register_handler(char* new_handler, IUnknown* handler_pointer)
{
   std::string handler_name(new_handler);
   if (std::find(_handler_cache.begin(), _handler_cache.end(), handler_name) != _handler_cache.end())
        return E_FAIL;

   void* register_interface = nullptr;

   if (handler_pointer->QueryInterface(_static_handle_proc_iid, &register_proc) == S_OK)
   {
      IHandlerRegister* handler_register = (IHandlerRegister)register_interface;
      handler_register->do_register(this);
   }
...
}
Rick
  • 11
  • 2
  • What does the 'queries the supplied interface for specific IID' look like? If the server is running as a higher-privileged user and it will instantiate _any_ COM interface passed to it, then this is a RCE and EOP. However, if its doing a check for the right IID it's going to depend entirely on what that check is doing. – Steve May 04 '20 at 15:32
  • Sorry, my COM jargon isn't really good. Because of that I'm probably unclear. The method takes an pointer of type IUnknown*, calls QueryInterface() on it with a hardcoded IID, and if it finds that interface it calls another method on it. The IUnknown pointer is under complete control of an untrusted application of lower privilege. If I pass it a pointer to a class that it normally doesn't handle it seems to create a proxy for it. But any calls on that object are sent back to the lower privileged process. And the results passed back to the higher privileged process by the standard marshaller. – Rick May 04 '20 at 19:03

1 Answers1

1

I think I found my answer after more reverse engineering and watching old CON talks. This answer represents my current understanding of the situation.

In theory this operation is safe, But there are a number of caveats. I think I found an improvement in the my server, since I initially did not have specific security settings enabled.

First, what actually happens when I pass an object to the COM server is that the COM subsystem will begin the process to marshal my object and provide a proxy for the server to call.

The COM client (the attacker) can fully control the object, RPC data and marshaled interface.

In the COM server (assuming Out of process servers, as is the case here) the RPC runtime will call CoUnmarshalInterface on the data provided by the client to create a proxy. This function will first determine if the sent object uses custom marshalling. Since I was mainly interested in this functionality I did not verify what happens when a standard object is unmarshalled.

Custom marshalling is used on objects that provide their own serialization and de-serialization routines. The data stream provided by the client will contain the CLSID of an object that can be used to unmarshall the data further.

Before this is done, however, the COM subsystem will check if it has been initialized to allow custom marshalling.

Calling CoInitializeSecurity with dwCapabilities set to EOAC_NO_CUSTOM_MARSHAL will disable this feature.

Supposing that security has been initialized to allow custom marshalling the process continues.

The server will now call VerifyUnmarshalerClsidForRestrictedInternalInterface this function checks if the unmarshal class specified by in OBJREF.u_objref.clsid is restricted in some sense.

A next follows in DetermineEffectiveUnmarshalingPolicyForStream and CheckAllowedByPolicy which will make the final determination if the custom marshaller will be loaded.

The unmarshal clsid specified by the attacker must inherit from IMarshal. The object is then instantiated and it's IMarshall interface by a call to CoCreateInstance. The rest of the function deals with actually unpacking the object with calls to UnmarshalInterface and similar.

I found this talk very helpful in understanding COM more under the hood: https://vimeo.com/214856542

These documentation pages were also quite helpful:

https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-counmarshalinterface

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dcom/fe6c5e46-adf8-4e34-a8de-3f756c875f31

Rick
  • 11
  • 2