COM


The Component Object Model (COM) and Distributed Component Object Model (DCOM) facilities in Windows provide a framework for developing language- and location-independent components. These components can be created and accessed from within a process, between different processes on the same computer, or remotely over a network.

Note

COM has become an umbrella term that encompasses DCOM (remote COM) and other COM-related technologies. Previously, the term COM referred to object access and manipulation between different processes on the same computer; DCOM extended this functionality to make objects accessible over the network. Presently, they can all be referred to as COM technologies.


COM is essentially an object-oriented wrapper for RPC; in fact, DCOM uses RPC for method invocation and communication. For the purposes of this discussion, COM and DCOM are viewed more as extensions of RPC. These similarities can help you apply what you've already learned about RPC.

COM: A Quick Primer

The following sections give you a brief rundown of the COM architecture, in case you have limited experience with COM programming. These basics are essential to understanding the information that follows on potential security issues in COM applications.

Components

COM promotes the development of reusable components, much like the use of classes in object-oriented programs. Each component provides an interface (or several interfaces) that describes a series of methods for manipulating the object. In the context of COM, "interface" refers to a contract between COM objects and their clients. This contract specifies a series of methods the object implements.

There are some major differences between a COM object and a class in an object-oriented program. COM objects are already precompiled and are accessible system-wide to any process that wants to use them. They are language independent and available to any application without having to be recompiled. Indeed, COM is a binary specification of sorts; it requires that objects export interfaces in a certain manner but doesn't care about the internal structure of how those objects can be implemented. In addition to being accessible to any language, COM objects can be implemented in a variety of languages; their internals are irrelevant as long as they adhere to their contracts.

COM objects are uniquely identified on the system by a globally unique identifier (GUID) called a class ID (CLSID). When a COM object is registered on the system, it adds a key to the registry with the same name as the object's CLSID. This key is stored in HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID.

Note

The HKEY_CLASSES_ROOT key is an alias for the HKEY_LOCAL_MACHINE\Software\Classes\CLSID, so the same CLSIDs can also be found at HKEY_CLASSES_ROOT\CLSID.


These keys are installed so that the COM subsystem can locate and instantiate objects as they're requested. You can view registered COM objects on the system with the Registry Editor (regedit.exe), shown in Figure 12-2.

Figure 12-2. Viewing COM objects with Regedit


As you can see, quite a few subkeys and values are installed for each CLSID; they're described as needed in the following discussion.

Because CLSIDs are hard to remember and aren't meaningful to people, COM objects often have namesaliases that can be used to refer to the object in place of the CLSID. These aliases are called program IDs (ProgIDs) and are entirely optional. A program ID is stored in the ProgID value in the HKEY_LOCAL_MACHINE\Software\Classes\CLSID\<CLSID> key. A program ID can have any format, but the MSDN-recommended format is Program.Component.Version. For example, one of the Microsoft Excel component is named Excel.Sheet.8. Of course, it would take a long time to look up program IDs if every CLSID key were queried to see whether its ProgID matches a request, so another key is used for forward lookups: HKEY_LOCAL_MACHINE\Software\Classes\<ProgID>. This key has a CLSID value that points to the ProgID's associated class.

COM objects operate in a client/server architecture; the endpoints of a COM connection can be different threads in the same process, threads in different processes, or even on different systems. An exposed COM interface is accessed in much the same way an RPC function is called. In DCOM, this launching process includes starting applications if necessary, applying security permissions, and registering DCOM applications as being available on certain endpoints.

A COM object can be an in-process server or out-of-process server. In-process servers are implemented in DLLs that are loaded into the client process's address space on instantiation. For the most part, you don't need to worry about in-process servers because they are in the caller's address space and security context. Of course, ActiveX controls represent a special case of an in-process server, and they are discussed in "ActiveX Security" later in this chapter.

An out-of-process server, however, runs inside its own process space. There are two types of out-of-process servers: local servers on the same system as the caller and remote servers on another machine. Communication is performed via IPC primitives exposed by the COM runtime. In fact, DCOM uses RPC to transport messages behind the scenes. An out-of-process server can potentially run in a different context from the client, so it might have additional security considerations.

Interfaces

The whole point of COM objects is that they expose interfaces that are accessible to any clients that can use their functionality. A COM object can expose any number of interfaces, which consist of a series of functions related to the task. Each interface has a registered interface ID (IID) that uniquely identifies the interface. IIDs are recorded in the registry at HKEY_CLASSES_ROOT\Classes\Interface\<Interface ID>.

This key contains a series of subkeys for each registered interface. As a code auditor, you need to examine these interfaces to see what attack surface they expose.

Each COM interface is derived directly or indirectly from a base class called IUnknown, which provides a generic method of interaction with every COM object. Every COM object must provide an interface with the following three methods:

  • QueryInterface() Used to retrieve a pointer to a COM interface, given the IID of that interface

  • AddRef() Used to increment the reference count of an instantiated object

  • Release() Used to decrement the reference count of an instantiated object and free the object when the reference count drops to zero

The QueryInterface() method is the real core of the IUnknown interface. It provides the capability to acquire instances of other interfaces the COM object supports. When reading COM documentation and technical manuals, you often encounter references to IUnknown. For example, the CoCreateInstance() function takes LPUNKNOWN type as a parameter, which allows the function to create an instance of any COM object because all COM objects are derived from IUnknown.

Application IDs

A collection of COM objects is referred to as a COM application or component. Each COM application has a unique ID, called an AppID, used to uniquely refer to a COM application on the system. Like CLSIDs, AppIDs are installed in the registry and contain a number of subkeys and values for per-application security settings. The AppID key provides a convenient location for enforcing security for applications hosting multiple COM objects. AppID keys are located in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AppId.

Note

AppID keys are also accessible at HKEY_CLASSES_ROOT\AppId.


Mapping CLSIDs to Applications

You've learned how to look up registered COM objects in the registry, but how do you find the implementation of each object? This information can also be found in the registry. The HKEY_LOCAL_MACHINE\Software\Classes\CLSID\<CLSID> keys have one or more of the following values, depending on the threading capabilities of the COM object. The values of interest are as follows:

  • InprocHandler32 or InprocHandler Used to indicate a handler DLL that provides the COM API interface; this DLL is normally ole32.dll (or ole2.dll for 16-bit servers). It's rare, although possible, for a COM server to specify its own handler.

  • InprocServer32 or InprocServer Used to indicate a server DLL that houses the implementation of the COM object. This value is used when the COM object is an in-process server.

  • LocalServer32 or LocalServer Used to indicate an executable that houses the implementation of the COM object. It's used when the COM object is an out-of-process server.

OLE

Object Linking and Embedding (OLE) is the predecessor to modern Windows COM. The original version of OLE uses DDE to allow interaction between components of different applications. This functionality is still part of the basic COM infrastructure, although it doesn't affect the discussions of DCOM. However, it's worth mentioning this relationship because the term "OLE" appears in many COM functions and data types.

Automation Objects

Automation objects are a special subclass of COM objects that originally provided a simpler form of IPC for controlling another application (referred to as an automation server). For example, Internet Explorer and Microsoft Word expose automation interfaces that allow clients to completely control the application and documents it contains. Automation servers generally expose scriptable methods, which are methods called through an IDispatch interface accepting VARIANT arguments. This interface is compatible with scripting languages because it doesn't use language specific elements such as object vtables and typed parameters. When a script invokes a method on an object, the scripting engine can use the IDispatch interface to ask for the unique ID of a method. The ID is then passed along with an array of VARIANT arguments via the IDispatch::Invoke() method.

Threading in COM

Windows evolved from a simple single-threaded OS to a true multiuser, multithreaded OS. This evolution has required some scaffolding to allow older, thread-unsafe COM objects to function properly in multithreaded versions of Windows. This scaffolding is provided in the form of apartments.

The historical version of COM is the single-threaded apartment (STA); a COM process can have any number of STAs, with each one running on a separate thread. The STA uses DDE to perform method calls on objects, thus requiring a window message pump to function. The advantage of using the STA is that it synchronizes all messages processed by the application. This synchronization makes it fairly easy to implement a basic single-threaded COM object. From a security perspective, an STA COM object presents unique concerns only if it's running in a privileged context on an interactive desktop. These issues have been discussed previously in the sections on window messaging and shatter vulnerabilities.

The multithreaded apartment (MTA) is also referred to as the free threaded apartment; a COM process has at most one MTA shared across all MTA objects in the process. The COM subsystem makes direct use of the object vtable when dispatching methods in an MTA, so it doesn't require any mechanism for handling window messages. Of course, this means COM method calls provide no guarantee of sequencing or serialization for an MTA.

A thread must set its apartment model before calling any COM functions. This is done by calling CoInitializeEx(), which has the following prototype:

HRESULT CoInitializeEx(void *pReserved, DWORD dwCoInit)


The dwCoInit argument dictates whether the thread enters a new STA or enters the process's MTA. It can take the following values:

  • COINIT_MULTITHREADED Indicates the thread enters the MTA.

  • COINIT_APARTMENTTHREADED Indicates the thread should create a new STA.

Of course, an in-process server has no way of knowing what model its client process is using, so it can't rely on CoInitializeEx() for properinitialization. In this case, the in-process server must specify at registration what threading models it supports, which is done in the registry value HKEY_CLASSES_ROOT\Classes\<CLSID>\InprocServer32\ThreadingModel.

The in-process server can specify one of three options in this value:

  • Apartment The STA model.

  • Free The MTA model.

  • Both An STA or MTA.

When an object is created, the COM runtime examines this registry key and tries to put the object in an existing MTA. If the correct apartment isn't present, COM creates a new one of the required type. If this value isn't present, the COM runtime assumes the in-process server requires the STA model.

Threading issues come into play when more than one thread can operate on an object; that is, more than one thread is in the same apartment as the object. This issue occurs in-process when both the client and server run in an MTA; however, it can occur out-of-process with an MTA server accessed by more than one client of any type. In both cases, COM developers must make the server object thread safe because any number of threads can be operating on it simultaneously.

One more important detail on COM threads is how the COM subsystem manages threads. Like RPC, the COM subsystem manages calls via a pool of worker threads. This means a call can occur on any thread, and developers can't assume that calls in sequence occur on the same thread. So a COM MTA can have no thread affinity, which means it can't make any assumptions about its thread of execution between calls. Threading issues in general are a complex topic, covered in depth in Chapter 13. Keep threading issues in mind when auditing COM objects in the MTA model.

Proxies and Stubs

COM objects can't directly call routines between different apartment models or across process boundaries. Instead, COM provides an IPC method in the form of proxies and stubs. Much like RPC requests, the COM subsystem handles calling remote components and marshalling data. In fact, DCOM uses the native Windows RPC mechanisms for its COM remoting.

On the client side, the code that bundles the data and sends it to the server is referred to as an interface proxy (or sometimes just "proxy") because it looks and acts exactly like the real object to the caller. The proxy has the same interface as the real object. The fact that the proxy is just a stand-in is transparent to the rest of the client application.

The server code responsible for decapsulating a request and delivering it to the server application is called a stub. A server application receives a request from a client stub and performs the necessary operations. It then returns a result to the stub, which handles all marshaling and communications.

Type Libraries

The easiest method of deploying and registering a COM component generally involves using type libraries. A type library describes all the interface and typing information for COM objects. It can include a variety of information, such as COM object names, supported interfaces, method prototypes, structures, enumerations, and relevant GUIDs for interfaces and objects. Developers can use type libraries to incorporate components into their applications with minimal effort.

Each type library can be registered with the system. Like interfaces and COM classes, they are given a unique GUID to ensure that each type library can be identified. Type library IDs are stored in the registry in HKEY_CLASSES_ROOT\Classes\Typelib, with subkeys identifying the location of the type library. In addition, CLSIDs and interfaces can indicate that a type library applies to them by using the Typelib subkey in their locations in the registry.

Type libraries can be in a standalone file (usually with the extension .tlb) or included as a resource in a DLL or executable. As you see later in "Auditing DCOM Applications," type libraries provide a wealth of essential information, especially when you don't have access to the source code.

DCOM Configuration Utility

The following sections focus on programmatic configuration of DCOM applications. You can also use the DCOM Configuration utility to view and manipulate the registered attributes of DCOM components. To run this utility, type dcomcnfg.exe at the command line or in the Run dialog box. In Windows XP and later, this command starts an instance of the Microsoft Management Console (MMC), as shown in Figure 12-3.

Figure 12-3. Viewing all registered DCOM objects


The DCOM Configuration utility can be used to manipulate all DCOM-related security settings, including the base subsystem security, default component security, and individual component security. This utility should be your starting point for reviewing an installed DCOM application. The Properties dialog box for a COM object shows you the application name, the application ID, security permissions associated with the object, and more useful tidbits of information you need to evaluate application exposure (see Figure 12-4).

Figure 12-4. Viewing properties of COM objects


DCOM Application Identity

Unlike local COM, a remote COM server often doesn't run under the access token of the launching user. Instead, the base identity is designated by the DCOM object's registration parameters. A DCOM server can run in these four user contexts:

  • Interactive user This context causes the application to run as whichever user is currently logged on. If no users are logged on, the application can't be started.

  • Launching user This context causes the application to run with the credentials of the user who's launching the server. If no identity is established in the registry, this context is the default setting.

  • Specified user This context causes the application to be launched by using a specific user's identity, no matter who the launching user is. The credentials of the target user are required to configure this context.

  • Service The application DCOM server is hosted inside a service and runs under a local service account.

Generally, running as the launching user is the simplest, most secure option. This context causes the application to impersonate the launching user; however, accessing objects across the network from the server fails in Windows 2000 and earlier because of the lack of impersonation delegation. Long-lived COM servers might require running under a local service account or a specified account. In Windows XP and later, the network service account is often used. Developers can also create a tightly restricted account for the DCOM object.

The most dangerous application identity is probably the interactive user because any method of running arbitrary code results in unrestricted impersonation of the interactive user. This identity is especially dangerous if the COM interface allows remote access. If you encounter this identity setting, examine all interfaces closely. Pay special attention to any capabilities (intentional or otherwise) that allow code execution or arbitrary file and object manipulation.

DCOM Subsystem Access Permissions

Starting with Windows XP SP2 and Windows Server 2003 SP1, Microsoft provides granular system-wide access control for DCOM, which can be accessed through the DCOM configuration in the System Properties dialog box. To manipulate these system-wide settings, click the Edit Limits buttons on the Security tab. These configuration parameters supersede the default and component-specific settings, so they can be used to completely restrict DCOM access. The access rights are summarized in Table 12-5.

Table 12-5. COM Object Access Rights

Access Right

Meaning

COM_RIGHTS_EXECUTE

Allows users to make calls on a COM interface.

COM_RIGHTS_EXECUTE_LOCAL

Required to allow local clients to make calls on a COM interface.

COM_RIGHTS_EXECUTE_REMOTE

Required to allow remote clients to make calls on a COM interface.

COM_RIGHTS_ACTIVATE_LOCAL

Required to allow local clients to activate the interface.

COM_RIGHTS_ACTIVE_REMOTE

Required to allow remote clients to activate the interface.


The COM_RIGHTS_EXECUTE right is required for remote COM to function at all. The default assignment of the remaining rights allows only administrators to activate and launch remote COM objects. However, all users are allowed to launch local COM objects and connect to existing remote objects. Earlier versions of Windows support only the COM_RIGHTS_EXECUTE permission.

DCOM Access Controls

You've already learned how RPC can use native Windows access control mechanisms to provide fine-grained authentication and authorization. DCOM makes use of this same infrastructure for its own access control features. However, DCOM authorization comes into play in a slightly different manner: at activation time and call time.

Activation

A DCOM object must be instantiated before a client can receive an interface pointer to it and before any of its methods can be called by that client. Usually, this instantiationcalled activationis done via RPC. The RPC subsystem locates the DCOM server a client is trying to access and launches it if it's not already running.

The Service Control Manager (SCM) determines whether the requesting principal is allowed to launch the object by examining the launch permission ACL for the requested class. This ACL is maintained in the registry key HKEY_CLASSES_ROOT\APPID\<APPID>\LaunchPermission.

The LaunchPermission value might be absent if no special permissions are required. If so, the class inherits the default permissions. This ACL is stored in the system registry at HKEY_LOCAL_MACHINE\Software\Microsoft\OLE\DefaultLaunchPermission.

Note

A DCOM server can't set launch permissions programmatically for the current call. Generally, the installing application or system administrator sets these permissions programmatically or with the DCOM Configuration utility. Therefore, insufficient launch permissions fall into the operational vulnerability classification.


Invocation

After a DCOM object is activated, developers can apply additional levels of control by enforcing call-level security, which controls the principals allowed to make interface calls on a specific object. There are two ways to enforce call-level security: through registry key settings and programmatically. The first method involves consulting the registry. First, the ACL for the application is checked, which is in the registry key HKEY_CLASSES_ROOT\APPID\<APPID>\AccessPermission. If this value is absent, application access has no special security requirements, and the default ACL is applied from the Registry key HKEY_LOCAL_MACHINE\Software\Microsoft\OLE\DefaultAccessPermission.

These registry keys are set manually or via the DCOM Configuration utility. The other way to enforce call access permissions is programmatically with the CoInitializeSecurity() function:

HRESULT CoInitializeSecurity(PSECURITY_DESCRIPTOR pVoid,         LONG cAuthSvc, SOLE_AUTHENTICATION_SERVICE *asAuthSvc,         void * pReserved1, DWORD dwAuthLevel, DWORD dwImpLevel,         SOLE_AUTHENTICATION_LIST *pAuthList,         DWORD dwCapabilities, void * pReserved3)


The CoInitializeSecurity() function gives developers extensive control over the basic security of COM objects. The security measures this function puts in place are process wide; that is, if a process has multiple DCOM object interfaces exposed, all interfaces are affected by a call to this function. The first argument actually provides the majority of the security capability. Although the prototype indicates that this argument is a pointer to a security descriptor, it can also point to two other structures: an AppID structure or an IAccessControl object. When an AppID structure is specified, the relevant AppID is located in the registry and permissions are applied according to the subkey values stored there. An IAccessControl object is a system-provided DCOM object that supplies methods for enforcing restrictions on other interfaces. The client can call CoInitializeSecurity() only once, and any attempt to call it again fails.

Note

Remember that CoInitializeSecurity() restrictions are applied to every interface the calling process has registered.


In addition to security descriptor settings, quite a few other security restrictions can be put in place with CoInitializeSecurity(). The dwAuthLevel parameter can also be used to enforce certain authentication levels. DCOM uses the same authentication levels as RPC, so they aren't repeated here. Refer to the "RPC Servers" section earlier in this chapter for details on these authentication levels.

The downside of CoInitializeSecurity() is that it can be called only once and affects all DCOM calls in the current process. However, to modify authentication behavior on a per-proxy basis, clients can also use the CoSetProxyBlanket() function, which has the following prototype:

HRESULT CoSetProxyBlanket(IUnknown * pProxy, DWORD dwAuthnSvc,         DWORD dwAuthzSvc, WCHAR * pServerPrincName,         DWORD dwAuthnLevel, DWORD dwImpLevel,         RPC_AUTH_IDENTITY_HANDLE pAuthInfo,         DWORD dwCapabilities)


This function operates similarly to CoInitializeSecurity(), except the authentication parameters affect only the proxy indicated by the pProxy argument rather than every proxy interface a client uses. Also, unlike CoInitializeSecurity(), CoSetProxyBlanket() can be called more than once.

Impersonation in DCOM

DCOM allows servers to impersonate clients by using the underlying RPC implementation. A DCOM application enforces impersonation levels programmatically and through the use of registry settings. Registry settings provide initial security requirements, but they can be overridden programmatically while the application is running. You might have noticed that both CoInitializeSecurity() and CoSetProxyBlanket() have a dwImpLevel parameter. This parameter allows clients to specify the impersonation level, and it works just as it does in RPC. This parameter is simply passed to the underlying RPC transport, discussed earlier in this chapter. However, impersonation can be performed only if the authentication level is RPC_C_IMP_LEVEL_IMPERSONATE or higher; the default value is C_IMP_LEVEL_IDENTIFY.

In addition to the standard IPC impersonation issues, DCOM objects might be more at risk from impersonation attacks. As Michael Howard and David Leblanc point out in Writing Secure Code, a server application is likely to act as a client when an event source/sink pair is set up and interfaces are passed as arguments to a server process.

For those unfamiliar with sources and sinks, they are older COM mechanisms for handling asynchronous events through the use of connection points. A connection point is simply a communication channel an object can establish with another object. You've seen examples of the client making calls to a server and receiving a result immediately. Sometimes, however, the server needs to advise the client that an event has occurred. This event might be based on a user action, or it might indicate that a time-consuming operation is finished. In this situation, the client exposes its own COM interface and passes it to the server. When the server wants to indicate an event occurred, it simply calls a method in this interface. To do this, the server must be a connectable objectthat is, expose the IConnectionPoint interface (among several others). The server's outgoing interface for a connection point is called a source, and the client's receiving interface is called a sink. The problem with this process is that the server is now a client, and its impersonation level is just as important as the client's. If a malicious client connects to an unprotected server, it can use CoImpersonateClient() in its sink interface to steal the server's credentials. Remember, the server needs to set fairly lax permissions to be vulnerable to this type of attack, as in the following example:

BOOL InitializeCOM(void) {     HRESULT rc;     rc = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);     if(FAILED(rc))         return FALSE;     rc = CoInitializeSecurity(NULL, -1, NULL, NULL,             RPC_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IMPERSONATE,             NULL, 0, NULL);     if(FAILED(rc))         Return FALSE;     return TRUE; }


If a server (or a client) for a connectable object initializes COM security as in this example, impersonation vectors are a definite threat because they might allow connecting clients to steal credentials. This type of attack is one of the main reasons for Microsoft's introduction of COM cloaking and RPC_C_IMP_LEVEL_DELEGATE.

MIDL Revisited

MIDL was introduced in "Microsoft Interface Definition Language" earlier in this chapter. IDL is primarily intended to express RPC interfaces, but it can also be used to describe COM interfaces. In fact, the MIDL compiler has language support for the Object Description Language (ODL), which can be used to represent objects as well as RPC interfaces. When auditing COM applications, you might see some COM object interfaces expressed in IDL, so this section reviews some of the main attributes and keywords for expressing COM objects.

The most important difference between COM ODL and RPC IDL is the presence of the object attribute in the IDL header. This keyword indicates that the interface is a COM object and directs the MIDL compiler to generate a COM proxy and stub, as opposed to RPC client/server stubs. The other main difference is indicating that the interface is derived from another interface. Remember that all COM objects are derived from IUnknown; so you must indicate that in the interface definition.

Note

Instead of being derived directly from IUnknown, COM objects can be derived from another class. However, the parent class is directly or indirectly derived from IUnknown.


Putting this together, a sample COM interface definition in an IDL file might look something like this:

import "iunknwn.idl" [     object,     uuid(12345678-1234-1234-1234-123456789012), ] interface IBankAccountObject : IUnknown {     BOOL LoadDetails([in] PUSER_DETAILS userDetails);     BOOL GetBalance([out] PBALANCE balanceInfo);     BOOL GetHistory([out] PHISTORY historyInfo);     ... other methods ... }


As you can see, it looks a lot like an RPC interface definition. The most important part is locating all the available interface methods and determining what arguments they take. Then you must examine the implementation of each function to identify any vulnerabilities.

In addition to defining just the interfaces, objects themselves can also be expressed. The coclass keyword is used to represent a COM object. The class definition contains a list of interfaces the object implements. Returning to the previous example of the bank interface, the class definition would follow the interface definition and look something like this:

[     uuid(87654321-4321-4321-4321-210987654321),     version(1.0),     helpstring("Bank Account Class") ] coclass CBankAccount {     [default] interface IBankAccountObject; }


This simple example shows the definition of the COM class CBankAccount. This object's CLSID is indicated by the uuid attribute. This class implements only one interface: IBankAccountObject.

Note

The default attribute listed before the interface definition is optional and doesn't need to be there. It simply indicates that IBankAccountObject is the default interface for the CBankAccount class. Other interface-specific attributes can be used; for more information, read the COM section of the MSDN.


Reviewing the code for a class exposing multiple interfaces requires examining each interface separately because the interfaces' functionality might be exposed to untrusted (or semitrusted) clients.

Type library information is also generated by using MIDL. Specifically, the library keyword can be used to create a .tlb file, like so:

library libname {    importlib("stdole.tlb");    interface IMyInterface1;    coclass CClass;    ... other stuff you want to appear in the TLB ... }


This section doesn't delve into the syntax for library definitions. When you have the source code, the type library doesn't offer much additional information. After all, you already know the available objects and their interfaces from looking at the rest of the IDL data.

Active Template Library

The Active Template Library (ATL) is another approach developers can use for developing COM applications. It allows developers to define interfaces in their code and automatically takes care of many of the more tedious aspects of implementing COM interfaces. For example, ATL can be used to automatically generate the IUnknown member functions QueryInterface(), AddRef(), and Release(). It can also be used to generate code for several other interfaces, such as IClassFactory.

ATL is used extensively, so you need to be able to identify COM interfaces in ATL-generated code. As it turns out, this is easy. All you need to be familiar with is the COM_MAP macro used to define a COM object; a COM object definition using COM_MAP looks something like this:

BEGIN_COM_MAP(CObjectName)     COM_INTERFACE_ENTRY(IMyInterface1)     COM_INTERFACE_ENTRY(IMyInterface2) END_COM_MAP()


Simple, right? You can easily see that the COM object CObjectName is being declared, and it exposes two interfaces: IMyInterface1 and IMyInterface2. From there, all you need to do is locate the methods for each interface entry in the COM MAP. Each COM_INTERFACE_ENTRY() in the COM_MAP is an interface definition from an IDL file, which is generated by the development environment when ATL wizards are used. When ATL is used to auto-generate COM objects, you have the IDL data at your disposal as well.

Auditing DCOM Applications

Now that you're familiar with the general structure of COM programming and security measures, you need to walk through the most effective ways of auditing COM client and server programs. Auditing COM servers isn't too different from auditing RPC servers; you need to address the following questions:

  • Are sufficient access controls in place to restrict the interface to authorized parties?

  • Are the exposed interface functions secure?

  • Is impersonation being used properly, or does it pose a risk?

  • What launching rights are granted to the server?

  • Are there any threading or synchronizations issues that could be exploited?

You can break down this list of requirements into the following steps:

1.

Check DCOM application security settings programmatically or by using the DCOM Configuration utility.

2.

Examine how CoInitializeSecurity() is called (if it's called) to back up your findings from the registry. This step also sheds some light on what sort of impersonation defaults are enforced.

3.

Locate the interface routines exposed by the COM server and apply the standard vulnerability-auditing methods you've learned in this book.

When determining the security of interface functions, you should look for the issues described in the following sections.

COM Registration Review

Now that you know how access controls can be applied to COM objects, it should be evident that determining whether access controls aren't secure is a two-step process: examining the activation access controls and examining the call-level access controls.

Activation access controls aren't in the application code; they reside in the registry. Although you might not have access to the target machines the application will be installed on, an install procedure should be in place to govern who can activate the object.

COM applications are often self-registering. That is, they can perform their own registration automatically so that manual setup isn't required. To do this, they export a pair of functions, DllRegisterServer() and DllUnregisterServer(), in one of the binary files bundled with the application. The DllRegisterServer() function contains code to make registration settings. The DllUnregisterServer() function does the reciprocalremoving all registration established in DllRegisterServer().

A COM application providing this interface is installed and removed with the regsvr32.exe program. When this program starts, it locates the DllRegisterServer() routine in the specified binary and runs it, thus removing the requirement for manual registration.

Note

ActiveX controls are self-registering COM objects. This just means users don't need to run the regsvr32 application because Internet Explorer does so automatically when downloading a new component. ActiveX controls are covered in "ActiveX Security" later in this chapter.


After the application is installed, you can use standard Windows utilities to inspect security settings. The easiest approach is to use the DCOM Configuration utility; however, the associated registry keys can be manipulated directly. These keys are located at HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AppID\<AppID>. Table 12-6 lists the MSDN-provided values that affect a server's DCOM security parameters.

Table 12-6. COM Registry Values

Named Value

Description

AccessPermission

Sets an ACL that determines access.

ActivateAtStorage

Configures client to activate on the same system as persistent storage.

AppID

Identifies the AppID GUID that corresponds to the named executable.

AuthenticationLevel

Sets the authentication level for the AppID, overriding LegacyAuthenticationLevel. Available only on Windows NT 4.0 SP4 and later versions.

DllSurrogate

Specifies that a DLL server is to use a surrogate.exe file. If the path is not specified, the system-provided surrogate is used.

DllSurrogateExecutable

Specifies that a DLL server is to use a custom surrogate.exe file. If the custom file is not specified, the system-provided surrogate is used.

Endpoints

Configures a COM application to use a specified TCP port number for DCOM communications.

LaunchPermission

Sets an ACL that determines who can launch the application.

LocalService

Sets the application as a Win32 service.

RemoteServerName

Sets the name of the remote server.

RunAs

Sets an application to run only as a given user.

ServiceParameters

Sets parameters to be passed to a LocalService on call.

SRPTrustLevel

Sets the trust level of the software restriction policy (SRP). Available only on Windows XP and later versions.


You have already seen that you can determine the launching identity of a COM application by checking the RunAs and LocalService keys listed in Table 12-6. These keys are usually absent, so the default action is taken, which causes the COM application to run in the context of the launching user. Running in this context roughly equates to a standard local process execution and generally requires no further inspection. However, further inspection is needed if the COM subsystem allows remote users to launch COM objects, as vulnerabilities in these methods could result in remote process execution. The remaining options might require far more inspection, particularly long-lived DCOM applications that run inside services.

Auditing COM Interfaces

Auditing the actual implementation of COM objects is one of the most critical components of auditing a COM-based application. After all, a vulnerability in the implementation of the functions could allow attackers to undermine all external access controls and the underlying system's integrity. The choice of authentication and impersonation parameters can reduce the impact of attacks. However, all exposed interfaces still need to be audited for the general classes of vulnerabilities discussed elsewhere in this book.

COM Source Audits

Auditing the source code makes your review easier because you can read interface definitions from IDL files or read the ATL definitions. From there, you can refer to the source code to find the implementation of relevant functions and determine whether the object exposes any vulnerabilities.

COM Binary Audits

You might be required to perform binary audits of COM applications. The principles for auditing a COM application (and indeed any application) are the same whether you have the binary or source code. However, the extra steps in the binary audit can be a major hurdle. With that in mind, this section gives you a brief summary of identifying and auditing COM interfaces as they appear in binary files.

Say you're auditing a COM application, and you want to identify which interfaces the object exposes, what methods are available in each interface, and what type of arguments they take. The most useful source of information is type libraries, if they are available.

Note

Type libraries are always available for automation objects because the IDispatch interface needs to publish the information in them.


As mentioned previously, the type library information might be stored in a separate file. However, most often it's stored as a resource in the executable or DLL that implements the object. You can find the location of a type library by consulting the HKEY_CLASSES_ROOT\CLSID\<CLSID>\TypeLib key.

Note

The HKEY_CLASSES_ROOT\Interface key can also contain a TypeLib key.


This key provides a TypeID GUID value that matches a subkey in HKEY_CLASSES_ROOT\TypeLib. This key has a version subkey indicating the location of the type library. If it's embedded in an executable, you can simply view it with a PE resource viewer (such as PE Editor at www.heaventools.com). This library information is especially useful because it gives you GUIDs, structure definitions, methods exposed by interfaces, and even type information for arguments to those methods.

After you have this information, you need to determine how to find the methods to audit in the binary. The first method is by locating entry points. An executable that implements a COM object must register each class object by using the CoRegisterClassObject() function. This requires indicating a CLSID along with a pointer to the class's IUnknown interface. By locating instances of CoRegisterClassObject(), you can find the vtable for IUnknown and then read the QueryInterface() function to learn about other interfaces the object exposes.

In fact, the QueryInterface() function exported by an object is always useful because it must return pointers to all its supported interfaces. So another way to locate functions exported by an object is to find the QueryInterface() implementation in the COM server to see how it handles requests for different IIDs. Remember, access to any interface other than IUnknown is done via the QueryInterface() function, so the implementation always looks something like this:

HRESULT QueryInterface(REFIID iid, void **ppvObject) {     if(iid == IID_IMyInterface1)     {         *(IMyInterface1 *)ppvObject = this;         AddRef();         return NOERROR;     }     *ppvObject = NULL;     return E_NOINTERFACE; }


Because the second argument always points to an interface upon success, you can find every assignment for this argument and deduce which functions are exported. Take a look at a practical example. The following disassembly is taken from C:\Windows\System32\wiaacmgr.exe, which hosts a COM server on a Windows XP machine (CLSID 7EFA65D9-573C-4E46-8CCB-E7FB9E56CD57). The code is divided into parts so that you can see what's going on more easily.

In this first part, the QueryInterface() function is initialized. As you can see, all that's done at this point is setting the ppvObject parameter to NULL so that it doesn't initially point to any interface:

.text:010054C5 QueryInterface proc near ; CODE XREF: .text:0100A7F7j .text:010054C5                 ; DATA XREF: .text:off_100178Co .text:010054C5 .text:010054C5 this_ptr       = dword ptr 8 .text:010054C5 riid           = dword ptr 0Ch .text:010054C5 ppvObject      = dword ptr 10h .text:010054C5 .text:010054C5    mov    edi, edi .text:010054C7    push   ebp .text:010054C8    mov    ebp, esp .text:010054CA    mov    edx, [ebp+ppvObject] .text:010054CD    push   ebx .text:010054CE    push   esi .text:010054CF    mov    esi, [ebp+riid] .text:010054D2    push   edi .text:010054D3    xor    ebx, ebx .text:010054D5    push   4 .text:010054D7    pop    ecx .text:010054D8    mov    edi, offset IID_IUnknown .text:010054DD    xor    eax, eax .text:010054DF    mov    [edx], ebx     ; *ppvObject = NULL;


This next part of the code compares the riid argument against IID_IUnknown. If the comparison succeeds ppvObject is set to point to the current (this) object. The jmp instruction at the end jumps to the function epilogue, which returns a successful result:

.text:010054E1    repe cmpsd .text:010054E3    jnz  short loc_10054F2                              ; test for IID_IUnknown .text:010054E5 .text:010054E5 loc_10054E5:  ; CODE XREF: QueryInterface+3Cj .text:010054E5    mov  eax, [ebp+this_ptr] .text:010054E8 .text:010054E8 loc_10054E8:  ; CODE XREF: QueryInterface+5Bj .text:010054E8    mov   [edx], eax    ; *ppvObject = this; .text:010054EA    mov   ecx, [eax] .text:010054EC    push  eax .text:010054ED    call  dword ptr [ecx+4] ; call AddRef() .text:010054F0    jmp   short loc_100552A


Evidently, this object has two interfaces in addition to IUnknown. This next part of the code compares the riid argument against two more interface IDs. If there's a match, the ppvObject parameter is set to the this object pointer and a successful return happens:

.text:010054F2 loc_10054F2:    ; CODE XREF: QueryInterface+1Ej .text:010054F2    mov    esi, [ebp+riid] .text:010054F5    push   4 .text:010054F7    pop    ecx .text:010054F8    mov    edi, offset IID_Interface1 .text:010054FD    xor    eax, eax .text:010054FF    repe cmpsd .text:01005501    jz     short loc_10054E5 ;test IID_Interface1 .text:01005503    mov    esi, [ebp+riid] .text:01005506    push   4 .text:01005508    pop    ecx .text:01005509    mov    edi, offset IID_Interface2 .text:0100550E    xor    eax, eax .text:01005510    repe cmpsd             ; test IID_Interface2 .text:01005512    jnz    short loc_1005522 ; go to failure .text:01005514    mov    eax, [ebp+this_ptr] .text:01005517    lea    ecx, [eax+4] .text:0100551A    neg    eax .text:0100551C    sbb    eax, eax .text:0100551E    and    eax, ecx .text:01005520    jmp    short loc_10054E8 ; *ppvObject = this;


Note

The second interface causes ppvObject to be set to the this pointer with 4 added to it.


If there's no match, the riid argument is deemed invalid, and the jnz instruction bolded in the previous code causes a jump to an error epilogue that returns the error E_NOINTERFACE, as shown in the following code snippet:

.text:01005522 loc_1005522:    ; CODE XREF: QueryInterface+4Dj .text:01005522    and    dword ptr [edx], 0 .text:01005525    mov    ebx, 80004002h ; E_NOINTERFACE .text:0100552A .text:0100552A loc_100552A:    ; CODE XREF: QueryInterface+2Bj .text:0100552A    pop    edi .text:0100552B    pop    esi .text:0100552C    mov    eax, ebx .text:0100552E    pop    ebx .text:0100552F    pop    ebp .text:01005530    retn   0Ch .text:01005530 QueryInterface endp


By finding QueryInterface(), you can figure out what interfaces are available based on how the ppvObject parameter is set. You don't even have to read the QueryInterface() code in many cases. You know that QueryInterface() is part of the IUnknown interface, and every COM interface must inherit from IUnknown. So vtable cross references to QueryInterface() are often COM interfaces, allowing you to focus on finding all cross-references to the QueryInterface() function. In the preceding code, there are two cross-references to QueryInterface(), which fits with what you learned from examining the code. Following one of these cross-references, you see this:

.text:0100178C off_100178C    dd offset QueryInterface ; DATA XREF: sub_100A6B7+Do .text:0100178C                           ; sub_100A9AF+13o .text:01001790    dd offset sub_1005468 .text:01001794    dd offset sub_1005485 .text:01001798    dd offset sub_1005538 .text:0100179C    dd offset sub_1005582 .text:010017A0    dd offset sub_10055CC .text:010017A4    dd offset sub_100ACA1


This code is a table of function pointers, as you expected, for one of the COM interfaces the object exposes. The two functions under QueryInterface() are AddRef() (sub_1005468) and Release() (sub_1005485): the other two IUnknown functions. These three functions are always at the top of every exposed COM interface vtable.

Similarly, DLL objects need to expose the DllGetClassObject() function. The responsibility of this function is to provide an interface pointer for an object, given a CLSID and an IID. Therefore, by reading through this function, you can find what classes are supported as well as what interface IDs are supported on each object. Typically, DllGetClassObject() implementations look something like this example taken from MSDN at http://windowssdk.msdn.microsoft.com/library/en-us/com/html/42c08149-c251-47f7-a81f-383975d7081c.asp:

HRESULT_export  PASCAL DllGetClassObject         (REFCLSID rclsid, REFIID riid, LPVOID * ppvObj) {     HRESULT hr = E_OUTOFMEMORY;     *ppvObj = NULL;     CClassFactory *pClassFactory = new CClassFactory(rclsid);     if (pClassFactory != NULL)  {         hr = pClassFactory->QueryInterface(riid, ppvObj);         pClassFactory->Release();     }     return hr; }


An object is usually instantiated and then queried for the specified IID. Therefore, initialization functions are commonly called from DllGetClassObject(), which sets up vtables containing the COM object's exposed methods.

There are certainly other methods for finding object interfaces, although sometimes they're less precise. For example, if you know the IID of an interface you want to find an implementation for, you could simply do a binary search for some or all of that IID, and then follow cross-references to methods using that IID. Often a cross-reference points to the QueryInterface() routine where that IID can be requested.

Automation Objects and Fuzz Testing

Automation objects are required to publish type information from their type libraries. This means clients can learn about all the callable methods and argument types they take just by asking the object for its type information. Therefore, by having a client that asks for this information and then using it to stress-test each available method, you could quickly find vulnerabilities in the application.

It turns out that a tool exists to do just this. Frederic Bret-Mounet designed and developed the COMbust tool, which he spoke about at the Blackhat Briefings conference in 2003. This tool takes any automation object specified by a user and does some basic fuzz testing on any methods it identifies. It's configurable, so users can tune it to test for specific conditions, and is available at www.blackhat.com/html/bh-media-archives/bh-archives-2003.html.


Another easy way to locate a QueryInterface() implementation without reading any code is to do a text search on the relevant binary code for the E_NOINTERFACE value (80004002). Any match for this number is usually a QueryInterface() implementation returning an error or a client checking for this error when it has called QueryInterface() on an object. By the context of the match, you can easily tell which it is.

ActiveX Security

An ActiveX control is simply a self-registering COM object deployed inside another application, such as a Web browser. The "Active" part of the name comes from the fact that these objects can register themselves, thus simplifying their deployment. Most ActiveX controls also expose IDispatch interfaces so that they can be instantiated and manipulated easily by scripting languages. Generally, these controls are hosted in Internet Explorer, although they can be hosted inside any application. ActiveX is an important Windows technology with serious security implications explored in the following sections.

Note

Changes to Internet Explorer 6 and the upcoming Internet Explorer 7 do a lot to mitigate the dangers of ActiveX controls. Internet Explorer 7 introduces site-based opt-in for controls to prevent a malicious site from instantiating installed controls.


ActiveX Code Signing

An ActiveX control is just a bundle of binary code that runs in the context of instantiating user. Because of the potential danger of running native code, Microsoft designed ActiveX controls to support validation through an Authenticode signature. Developers can sign controls with their private keys, and users can validate the source of the unmodified control. This signature doesn't in any way state that the control is free of vulnerabilities, and it doesn't prevent the control from being malicious. It just means there's a verifiable paper trail leading back to the developer.

Safe for Scripting and Safe for Initialization

In addition to code signing, ActiveX controls have a few additional parameters to limit their attack surface when deployed inside Internet Explorer. These parameters are termed "safe for scripting" and "safe for initialization." There are two ways to mark interfaces as safe. The first is performed at installation by modifying the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\<GUID of control class>\Implemented Categories\<GUID of category>. The safe for scripting category GUID is {7DD95801-9882-11CF-9FA9-00AA006C42C4}, and the safe for initialization category GUID is {7DD95802-9882-11CF-9FA9-00AA006C42C4}.

The second approach to marking a control as safe requires that the control implement the IObjectSafety interface, which exposes the GetInterfaceSafetyOptions() method to the hosting container. The hosting container calls this method to determine whether a specific interface is marked as safe for scripting or initialization and can also request that the control be marked as safe by calling the IObjectSafety.SetInterfaceSafetyOptions() method.

Any control marked as safe for scripting can be instantiated and manipulated in Internet Explorer. Microsoft advises marking a control as safe for scripting only if it must be manipulated from Internet Explorer and doesn't provide any means for unauthorized parties to alter the state of the local system or connected systems. This guidance is given because a safe for scripting control exposes its methods to any site users view, so attackers can leverage the functionally exposed by a control to exploit client users. For example, say a scriptable control allows the manipulation of arbitrary files. This issue might be part of a faulty design or the result of a vulnerability in path checking. Regardless, it would present an unacceptable vulnerability for an ActiveX control because it allows any remote attacker to drastically alter the victim's system after connecting to a malicious Web site. When reviewing ActiveX controls, you need to treat every scriptable method as attack surface and assess them as you would any other potentially vulnerable code.

ActiveX controls can also store and retrieve data between instantiations by using the IPersist interface, which is exposed to controls marked as safe for initialization. Microsoft advises marking a control as safe for initialization only if it must store persistent data internal to Internet Explorer and it handles this data properly. A security vulnerability can occur if the object stores sensitive data and exposes it to an untrusted source or if a control fails to treat persistent data as data originating from an untrusted source.

Some people might be a little fuzzy on why a control must be separately marked as safe for initialization. After all, the control is just a binary, so it can call any Windows API function on its own. This means it can read the registry or file system without the need for an IPersist interface, so exposing sensitive data is still a concern. However, a control can be initialized with parameters provided by a Web site, as shown in this HTML fragment that instantiates a control:

<OBJECT          CLASS         CODEBASE="MyControl.CAB">     <PARAM NAME="MyServer" VALUE="malicious.com" /> </OBJECT>


This fragment creates an instance of a control and attempts to initialize it with the MyServer parameter. This parameter is accepted through the IPersistPropertyBag interface, which inherits from the base IPersist interface. The control retrieves the parameter with the following code:

STDMETHODIMP MyControl::Load(IPropertyBag *pProps,        IErrorLog* pErrLog) {     _variant_t    myVar;     int           hr = 0;     hr = pProps->Read("MyServer", &myVar, pErrLog);     if (hr != 0) return hr;     strcpy(m_serverName, myVar);     return hr; }


This code is a simple implementation of the IPersistPropertyBag::Load() method. Internet Explorer calls this method when loading the control, and the control then retrieves the PARAM values via the IPropertyBag interface. What's important here is that you follow the path of these properties and see what they affect. The _variant_t class in this code has overloaded operators to handle type conversions, so don't be distracted by that part. Instead, just note that the bold line copies the property string into a member variable. Here's the declaration of that member variable:

char   m_serverName[512];


It's fairly obvious that this code is performing an unbounded string copy into a fixed-size buffer, so this particular IPersist interface is vulnerable to a straightforward buffer overflow. This vulnerability might seem obvious, but this exact pattern has been seen in more than one ActiveX control. The issue is that developers often don't consider control instantiation to be an exposure point. You need to pay special attention to all IPersist interfaces to see whether they handle input in an unsafe manner.

Site-Restricted Controls

One of the best ways of limiting a control's attack surface is to instantiate it only for a known set of locations. Implementations can limit instantiation based on hostname, but restrictions can be based on any connection information by implementing the IObjectWithSite interface and the SetSite() method. The WebBrowser control can then be used to provide detailed connection information. Microsoft provides the SiteLock template as a starting point for creating a site-restricted control.

If a control is locked to a particular site, you need to determine how effective that lock is. There might be issues in the string comparisons that allow you to bypass the checks, similar to the topics discussed in Chapter 8, "Strings and Metacharacters." There might also be Web application vulnerabilities at the hosting site that allow you to instantiate the control in the context of the site, but with your own parameters and scripting. Read Chapters 17, "Web Applications," and 18, "Web Technologies," for more information on vulnerabilities that involve this attack vector.

The Kill Bit

Sometimes a vulnerability is identified in a signed control. This control can then be delivered by a malicious Web site, allowing attackers to exploit a control that otherwise appears safe. A site-restricted control is less vulnerable to this type of attack; however, Web application vulnerabilities (such SQL injection and cross-site scripting) might allow attackers to exploit the underlying vulnerability. For this reason, Microsoft introduced the ActiveX kill bit, which is used to mark a control version as unauthorized. The kill bit is set by setting the CompatibilityFlags DWORD value to 0x00000400 in this registry location: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\ActiveX Compatibility\<GUID of control class>.

This key and value aren't usually present, so they need to be created by the control's installer. Developers often have a new control set this value for all previous versions, just to prevent earlier versions from being installed. Note whether this value is set; if it's not, you might want to look at vulnerabilities in previous control versions.

Threading in ActiveX

Most ActiveX controls are registered for the STA model, so thread synchronization issues aren't generally a problem. However, an ActiveX control can be registered as an MTA. This model is a bad idea from a usability perspective because it can cause GUI synchronization issues. However, an MTA control might also expose synchronization vulnerabilities.

Reviewing ActiveX Controls

Proprietary ActiveX controls are often frowned on in modern Web application development. They've mostly been replaced with newer technologies that are more portable and less prone to security issues. However, they are still deployed in many legacy and corporate intranet sites. As a reviewer, one of your first considerations should be whether a Web-hosted ActiveX control is necessary and determining the cost of replacing it.

If the control is necessary, review it as you would any other binary application. However, you also need to ensure that the control handles the considerations mentioned previously in this section. Here's a basic checklist:

  1. If you're reviewing the control as part of a larger system, check that it's signed with a certificate trusted by clients. If the control isn't signed, look for vulnerabilities in the rest of the system that could allow attackers to deploy a malicious control.

  2. If the control must be marked safe for scripting, evaluate all exposed IDispatch paths closely, including vulnerabilities resulting from the intended functionality and implementation vulnerabilities.

  3. If a control must be marked safe for initialization, evaluate all IPersist calls closely. Look for any exposure of sensitive data. Also, look for any mishandling of persistent data, such as conditions that could result in memory corruption.

  4. Check whether the control is site restricted. If it is, look for vulnerabilities in the restriction implementation that could allow it to be instantiated by another site. Also, check for any other implementation vulnerabilities that could make this interface exploitable. If the control is part of a larger system, look for Web application vulnerabilities that could be used to circumvent the site lock.

  5. Check to see whether the control sets the kill bit for previous versions. If not, you might want to do a cursory analysis for vulnerabilities in earlier versions of the control.

  6. If the control uses the MTA model, check for synchronization issues that could be exploited by scriptable methods.




The Art of Software Security Assessment. Identifying and Preventing Software Vulnerabilities
The Art of Software Security Assessment: Identifying and Preventing Software Vulnerabilities
ISBN: 0321444426
EAN: 2147483647
Year: 2004
Pages: 194

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net