Secure DCOM Best Practices

Secure DCOM Best Practices

DCOM is really just a wrapper over RPC that allows COM to operate across a network, so the preceding section on RPC security gives you the foundation for many of the concepts presented here. In addition to the problems of impersonation level and authentication level, DCOM adds launch permissions, access permissions, and the problem of the user context that the object will use. To add to the fun, there are at least three ways to do just about anything concerning security. Let's get started!

DCOM Basics

A good place to start is by opening the Dcomcnfg.exe application. On a system running Windows NT 4 or Windows 2000, you'll get the Distributed COM Configuration Properties dialog box, and on a system running Windows XP or later, a Microsoft Management Console (MMC) snap-in will show up, allowing you to look at both COM+ applications and DCOM objects. Figure 16-5 shows the Default Properties tab of the Distributed COM Configuration Properties dialog box in Windows 2000.

figure 16-5 the default properties tab of the distributed com configuration properties dialog box.

Figure 16-5. The Default Properties tab of the Distributed COM Configuration Properties dialog box.

First, you've got a choice whether to turn DCOM on or off for the entire system. This represents a fairly large hammer: be careful when using it, or things might break unexpectedly. If you turn DCOM off, there's not much point to the rest of this chapter, so I'll assume you've left it on. Next, you have the option of enabling COM Internet Services. COM Internet Services enable RPC over HTTP, turning your Web server into an RPC and DCOM provider. I wouldn't enable this option without doing some thinking about what management interfaces might also be made available over HTTP. Finally, the default authentication and impersonation levels are specified. These settings map exactly to the options you have available for RPC. The default authentication level is Connect, or RPC_C_AUTHN_CONNECT. The default impersonation level is Identify, which is the same as RPC_C_IMP_LEVEL_IDENTIFY.

The last item on the Default Properties tab is labeled Provide Additional Security For Reference Tracking. A little COM background is needed here: when an object is opened, you call IUnknown::AddRef, and when you're done with an object, you should call IUnknown::Release. Once an object has been released as many times as it has had IUnknown::AddRef called, the object decides it isn't needed any longer and unloads itself. Unfortunately, COM doesn't bother to check by default whether the caller is from the same process, so if a poorly written client or an attacker calls IUnknown::Release enough times, the object is unloaded, thus creating user astonishment, not to mention denial of service. If you enable additional security for reference tracking, you can avoid this problem, but be warned that you will also incur some overhead. If you're adding an application to someone's system, it might be rude to change the settings for all the other applications, so you should set the reference tracking security in the CoInitializeSecurity function by passing in the EOAC_SECURE_REFS value to the dwCapabilities argument.

The Default Security tab specifies default access, launch, and configuration permissions. Access permissions control the users who can access a currently running object, launch permissions control the users who can start an object that isn't currently running, and configuration permissions determine who can edit configuration information. Configuration information is especially sensitive because a DCOM application can be configured to run as the currently logged on user. Be aware that any user who can modify DCOM configuration can take action on the part of any other interactive user. The default settings allow only members of the Administrators and Power Users group to modify configuration settings. Unlike Windows NT, Windows 2000 Power Users should be thought of as Admins-Lite. It isn't a good idea to loosen these permissions from the default values, and if you'd like to tighten them, take care that you don't cause older applications to fail. A good test is to see whether an ordinary user can accomplish his tasks if he can, you can either reduce the Power Users permissions or consider just running all the users as an ordinary user.

The Default Protocols tab first became available in Windows NT 4, service pack 4, and allows you to regulate which protocols DCOM applications can use. In addition to being able to regulate protocols, you can also specify ranges of ports that can be used by the TCP or User Datagram Protocol (UDP) transports, known as Connection-Oriented TCP/IP and Datagram UDP/IP in the user interface. If you need to use DCOM across a firewall, being able to specify a specific port for an application or range of ports will make the firewall administrator a lot happier, and using TCP allows a firewall to regulate whether a connection can be created in one direction but not the other.

Application-Level Security

You can specify all the settings that are available for the entire system on an application basis. This can be accomplished by double-clicking an application on the Applications Tab of the Distributed COM Configuration Properties dialog box, or you can edit the registry directly by looking up the object ID in HKey_Local_Machine\Software\Classes\AppId. Note that if an application hosts more than one object, you'll have to apply the same settings for all the objects an application hosts. Depending on the permissions needed by the individual objects, you might end up having to apply permissions that are the least common denominator for all the objects hosted by the application. You can then try to impose different security settings on each object by using programmatic security, but this can get complicated and is prone to error. A good rule to use in this situation is that if two objects have very different security requirements, you should put them in two different applications or DLLs. In addition to the items that can be set for the entire system, an individual DCOM application can be configured to run under different user contexts. This is an important topic, and I'll cover it in depth in the next section. Finally, you can configure an individual object to use a specific port if either TCP or UDP is picked as a protocol. The ability to perform complicated transactions by using DCOM, coupled with the ability to run the transaction over only TCP port 135 and a specific port, makes it a better option than opening up a firewall completely between two systems. Note that datagram protocols are not supported starting with Windows 2000.

Some DCOM settings can be set only at the application level in the registry. Any setting that has to be set prior to application launch can't be set by the application itself. Specifically, launch permission, endpoint information, and user context must all be set in the registry.

DCOM User Contexts

Like a service, a DCOM object can run under a number of different user contexts. Your options are to impersonate the calling user; to run as the interactive user; to run as SYSTEM, which is available only to DCOM servers implemented as a service; and to run as a specific user. Unlike most of the people writing about DCOM security, I [David] have both a hacker's perspective and a security administrator's perspective. It's been my job both to break into things and to try to determine how to stop people from getting into things they should not here at Microsoft. The choices you make can have a huge impact on overall network security. Let's look at our various options, all of which have benefits and drawbacks.

Run as the Launching User

If a DCOM server executes as the calling user, security considerations are fairly simple. No user credentials get stored anywhere, and any actions performed can be checked against access controls normally. One major drawback is that prior to Windows 2000, it wasn't possible for one system to delegate calls to another system. If your DCOM object needs to access resources off the local host and you need to support Windows NT 4.0, running as the launching user won't work. Even if you're supporting only Windows 2000 and later, your security administrators should be cautious about flagging your system as trusted for delegation. Additionally, performance issues exist because each instance of your object that's running under a different user context will require a different window station, the object that hosts a desktop. See the Platform SDK documentation for more details.

Run as the Interactive User

Running as the interactive user is the most dangerous possible way to run a DCOM object, and I do not recommend it unless you're trying to write a debugging tool. First, if no one is logged on, the DCOM object won't run, and if the user logs off while you're running, the application dies. Second, it is a privilege-escalation attack waiting to happen. A number of API calls and other methods are available to determine when a user is logged on to the console of a computer. It would be fairly trivial to poll the system, wait for the administrator to log on, and then fire up the DCOM object and wreak mayhem. If you feel you absolutely must write a DCOM object that runs as the interactive user, make sure you notify the logged on user when the application starts, severely restrict the users who can launch and access the object, and be careful about the methods you expose.

Run as the Local System Account

DCOM objects that run as a service have the option of running as the local system account or, in Windows XP and later, the less-privileged network service account. Local system is the most powerful account on the system and can modify the operating system in any way. Network service isn't as powerful, but several services normally run under this context, so you still need to be careful. Be extremely careful with the interfaces you expose, and be prepared to impersonate the client to perform access checks. When your DCOM application is a SYSTEM service, make sure that the impersonation level on all the proxies it uses is Identify. Otherwise, your callees will elevate privilege. By default, DCOM impersonation level is Identify, but programmers routinely call CoInitializeSecurity or proxy security APIs and change it to Impersonate.

More Info
You should also be aware of the impersonation privilege added to Windows .NET Server. Refer to Chapter 7, Running with Least Privilege, for information regarding this new privilege.

Run as a Specific User

Running as a specific user is the way that Microsoft Transaction Server normally runs objects, and doing so has some nice benefits. If the user has domain scope, the object can take actions on other systems on behalf of the calling user. You'll also create a maximum of one window station per object, not one window station per caller. Any user account used for a DCOM object requires the Log On As A Batch Job privilege. If you assign the user by using Dcom cnfg.exe, it will grant the correct rights for you, but if you set it up in your application, be sure you grant your user the correct privileges. Be careful that domain policies don't overwrite the privileges you need.

The downside is worth thinking about. When a DCOM object runs as a particular user, the user account is recorded in the registry. No big deal the password is safe, right? For some value of safe, yes: it takes an administrator to run a tool that can dump the private data from the LSA. Now consider the case in which you've rolled out your application to over 3000 systems and the user account is an administrator on each of those systems. You now have 3000 computers that are each single points of catastrophic failure from a security standpoint for the entire group of 3000. Let's say that you've got a crack team of system admins who can maintain these systems such that they have 99.9 percent reliability from a security standpoint. Only on one day in 1000 days can any one system be completely compromised. Your overall chances of having the system of 3000 computers secure is given by (0.999)3000, which is approximately 5 in 100. So on only 18 days out of an average year, the hackers are going to be thwarted. If you have something less than a crack team of administrators, your odds are far worse.

One way to manage this risk is for your DCOM object to run under a nonprivileged user. Even so, if the system is supposed to access highly confidential data, such as human resources information, just obtaining the user credentials might be enough to be considered a problem. A second strategy is to reduce the number of systems running your object a set of 20 computers might be something you can really keep secure. A third approach would be to use different users for different groups of systems. That way a compromise of one group won't inevitably lead to the compromise of all the systems. If your object needs to run as a very high-level user to do its job, consider using a different account preferably a local user account on each system. The current Systems Management Server (SMS) client service takes this approach, and from a hacker's standpoint, it's boring. You compromise the system, obtain admin access, and then dump the secrets only to obtain the same level of access you already have. That's no fun! If you're a system administrator, I can assure you that if the hackers are having fun, you certainly are not going to have fun. Finally, Windows XP and Windows .NET Server can use the new LocalService and NetworkService accounts. These accounts don't require password management and don't have elevated privileges on the system.

Programmatic Security

DCOM also allows you to make security settings both at the server and at the client in your code. This can be accomplished by calling CoInitializeSecurity on either the server or the client side, and the client can also call IClientSecurity::SetBlanket to change the security settings for just one interface. COM seems to have its own language for many features, and the collection of security settings is known as the blanket. Let's review the parameters passed to CoInitializeSecurity:

HRESULT CoInitializeSecurity( PSECURITY_DESCRIPTOR pVoid, //Points to security descriptor LONG cAuthSvc, //Count of entries in asAuthSvc SOLE_AUTHENTICATION_SERVICE * asAuthSvc, //Array of names to register void * pReserved1, //Reserved for future use DWORD dwAuthnLevel, //The default authentication level //for proxies DWORD dwImpLevel, //The default impersonation level //for proxies SOLE_AUTHENTICATION_LIST * pAuthList, //Authentication information for //each authentication service DWORD dwCapabilities, //Additional client and/or //server- side capabilities void * pReserved3 //Reserved for future use );

The first parameter is the security descriptor. It can actually be used several different ways it can point to an actual security descriptor, an application ID (AppID), or an IAccessControl object. The call knows which you've passed by a flag set in the dwCapabilities argument. If you set it to an AppID, it will then take the information from the registry and ignore the remainder of the arguments. This determines who can access the object, and, once set by the server, the security descriptor can't be changed. This parameter doesn't apply to a client and can be NULL. The Platform SDK says in the fine print that if a server sets it to NULL, all access checking is disabled, even though we might still authenticate, depending on the dwAuthnLevel parameter. Do not do this.

Next, you get to choose an authentication service. Most applications should let the operating system figure this one out, and you'd pass -1 to the cAuthSvc parameter. Skip ahead to the dwAuthnLevel parameter this is where you'd set the required authentication level. As described in the Performance of Different Security Settings section, if you set the parameter to RPC_C_AUTHN_LEVEL_PKT_PRIVACY, the performance loss is small and the security gain is high. It's almost always a good idea to require packet privacy. When the client and the server negotiate the security settings, the highest level required by either the client or the server will be the end result.

The impersonation level isn't negotiated but is specified by the client. It makes sense that the client should be allowed to tell the server what actions are allowed with the client's credentials. There's one interesting way that the client and server can switch roles, so it's a good idea for the server to set this flag it could end up becoming a client! As recommended earlier, specify RPC_C_IMP_LEVEL_IDENTIFY or RPC_C_IMP_LEVEL_ANONYMOUS unless you're sure your application requires a higher-level impersonation value.

The dwCapabilities argument has several interesting values that could be useful. Both EOAC_STATIC_CLOAKING and EOAC_DYNAMIC_CLOAKING are used to enable cloaking on systems running Windows 2000 and later. Cloaking allows an intermediate object to access a lower-level object as the caller. If you're impersonating a caller, it's often best to access other objects under the context of the calling user; otherwise, you might be giving them access to some resources they shouldn't have available. You use EOAC_SECURE_REFS to keep malicious users from releasing objects that belong to other users. Note that this flag is incompatible with anonymous authentication.

As of Windows 2000, a new flag, EOAC_NO_CUSTOM_MARSHAL, can be specified. Specifying this flag contributes to better server security when using DCOM because it reduces the chances of executing arbitrary DLLs. EOAC_NO_CUSTOM_MARSHAL unmarshals CLSIDs implemented only in Ole32.dll and Component Services. A CLSID is a globally unique number that identifies a COM object. DCOM marshals references to objects by constructing object references (OBJREFs) that contain CLSIDs. CLSIDs are vulnerable to security attacks during unmarshaling because arbitrary DLLs can be loaded. Processes that have declared EOAC_NO_CUSTOM_MARSHAL in their security capabilities by calling CoInitializeSecurity can also use CLSIDs that implement CATID_Marshaler.

EOAC_DISABLE_AAA causes any activation in which a server process would be launched under the caller's identity (activate-as-activator) to fail with E_ACCESSDENIED. This value, which can be specified only in a call to CoInitializeSecurity, allows an application that runs under a privileged account (such as the local system account) to prevent its identity from being used to launch untrusted components. It can be used with systems running Windows 2000 and later.

If you'd like to play with the various settings and see how they work together, I've created a DCOM security test application see the DCOM_Security project with the book's sample files in the Secureco2\Chapter 16\DCOM_Security folder for the full source. First I created a fairly generic DCOM server by using Microsoft Visual C++ 6's Active Template Library (ATL) COM AppWizard, and then I added the ISecurityExample interface, which implements the GetServerBlanket method shown here:

STDMETHODIMP CSecurityExample::GetServerBlanket(DWORD * AuthNSvc, DWORD * AuthZSvc, DWORD * AuthLevel, DWORD * ImpLevel) { IServerSecurity* pServerSecurity; OLECHAR* PriName; if(CoGetCallContext(IID_IServerSecurity, (void**)&pServerSecurity) == S_OK) { HRESULT hr; hr = pServerSecurity->QueryBlanket(AuthNSvc, AuthZSvc, &PriName, AuthLevel, ImpLevel, NULL, NULL); if(hr == S_OK) { CoTaskMemFree(PriName); } return hr; } else return E_NOINTERFACE; }

As you can see, this is fairly simple code you just get the context of the current thread and query the blanket by using an IServerSecurity object. Once you obtain the results, pass them back to the client. The TestClient client queries the current client-side security settings, prints them, uses IClientSecurity::SetBlanket to require packet privacy on this interface, and then queries GetServerBlanket on the server. Here's a look at the results:

Initial client security settings: Client Security Information: Snego security support provider No authorization Principal name: DAVENET\david Auth level = Connect Impersonation level = Identify Set auth level to Packet Privacy Server Security Information: Snego security support provider No authorization Auth level = Packet privacy Impersonation level = Anonymous

Once you install and build the demonstration projects, copy both TestClient.exe and DCOM_Security.exe to another system. Register DCOM_Security.exe with the operating system by invoking it with DCOM_Security.exe /regserver. Be careful how you type it because the application built by the wizard won't tell you whether the registration succeeded. With just a little work, you can incorporate this test code into your own application to see exactly how your security settings are working. But be careful: you won't get a valid test by running the client and the server on the same system.

Sources and Sinks

DCOM has an interesting approach to handling asynchronous calls, although in Windows 2000 and later genuine asynchronous calls are supported. It allows a client to tell a server to call it back on a specified interface when a call completes. This is done by implementing a connectable object. Connection points are covered in detail in several books one good one is Inside Distributed COM (Microsoft Press, 1998), by Guy Eddon and Henry Eddon and you're best off consulting one of these for full details. The interesting aspect from a security standpoint is that the server has now become the client. If the server doesn't properly set its security blanket to prevent full impersonation, the client can escalate privilege. Imagine the following series of events with a server running under the local system account that normally impersonates a client. The client first advises the server of its sink and asks the server to complete a long call. When the server is done, the client accepts the call to its sink, impersonates the server, and proceeds to manipulate the operating system! I've browsed three different books on DCOM while researching this problem, and only one of them even mentioned that connectable objects can be a security problem. If you're implementing a server that supports connectable objects, be careful to avoid this pitfall.

Another way that this problem can occur is if one of your methods accepts an interface pointer (that is, a pointer to another COM/DCOM object). You also have to think about this problem if you call IDispatch::Invoke from inside your object. If someone could have tampered with the target object or, worse yet, you're invoking arbitrary objects, they might elevate privilege by impersonating you.



Writing Secure Code
Writing Secure Code, Second Edition
ISBN: 0735617228
EAN: 2147483647
Year: 2001
Pages: 286

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