Code for this article: Nov99SecurityBriefs.exe (25KB)
Keith Brown works at DevelopMentor, developing the COM and Windows NT Security curriculum. He is coauthor of Effective COM (Addison-Wesley, 1998), and is writing a developer's guide to distributed security. Reach Keith at http://www.develop.com/kbrown.
This issue marks the first anniversary of this column, and I figured I'd join Guy Eddon this month and slip in another installment that couples my favorite two topics: security and COM. In particular, I'd like to look at the way security works with a typical three-tier, Web-based application that uses Microsoft® Internet Information Server (IIS), ASP, and COM+/Microsoft Transaction Server (MTS) on the middle tier. While this type of application has become incredibly popular, many of the security mechanisms (especially on Windows NT 4.0) have remained shrouded in mystery.
To get the most benefit from the COM+/MTS infrastructure, many developers wisely tuck as much of their code as possible into configured components on the middle tier, using IIS and ASP simply as an HTTP gateway into these components. (If you've ever tried to make DCOM work out in the weird world, you know that HTTP is a much more natural match for the perils of the Internet; firewalls and address-translating proxy servers generally make mincemeat of DCOM.) In this particular scenario, there are some interesting security hurdles that must be addressed, and I'm going to show you some of the plumbing that makes it work while pointing out some tips and traps.
COM Client Identity
Security in COM is built on top of the Remote Procedure Call (RPC) security infrastructure, and revolves around being able to determine the identity of the principal making a method call. When a method call goes out on the wire, the RPC runtimes on the two machines exchange one or more packets, allowing the caller to prove her identity in a secure fashion. This procedure is known as authentication. But if a thread in some process makes an outgoing COM call, how does the RPC runtime figure out exactly who to say the caller is in the first place? To understand this, you'll need to take a step back and think about how the operating system itself figures out the principal associated with a given thread of execution.
When a thread makes a call into any system API, the system must be able to determine on whose behalf that thread is executing. Otherwise, the system wouldn't know who to charge the operation to (in other words, whose name should go in the audit log) or who to perform access checks for (maybe Alice is granted access, but Bob is denied).
Normally all threads within a process execute on behalf of the same principal, the principal on whose behalf the process itself is running. This is indicated by an access token associated with the process, which specifies the security context of the process. Whenever one process starts another process (ultimately via a call to CreateProcess), the new process simply takes a copy of the creator's access token, thus naturally propagating the identity of the original process. (I'm specifically ignoring CreateProcessAsUser and friends for the sake of this discussion.)
So when you start an application from Explorer, on whose behalf does the new process run? It runs as the same principal as Explorer's process. When you launch an application from a command prompt, the new process runs as the same principal as the command prompt's process. For most client applications, the root of all this token propagation starts when a client logs in via Winlogon, which produces a token for that client and starts the shell process (Explorer by default) running as the client. So given this, the natural order of the Windows® security model dictates that all threads in all processes started either directly or indirectly by the interactive user (Alice, for example) will naturally run as Alice.
As with most rules, this one was made to be broken. From birth, a thread naturally runs in the same security context as the enclosing process, but later it may choose to switch security contexts via impersonation. Impersonation is the act of associating an access token with a thread in order to override the natural process-wide access token. So even though Alice may have started a process, internally that new process may start threads that choose to run as Bob and Mary. Now this isn't the norm for GUI applications, but daemons running on the Windows platform in the middle or back tier often use impersonation as an integral mechanism for discovering security information about a clientthe most fundamental question being the identity of the client principal.
Figure 1 COM and Impersonation
So given a daemon process running in the middle tier (as Bob), and a thread in that process that is currently impersonating a client (Alice), when that thread makes an outgoing COM call to the back end, whose credentials will COM use to make the outgoing call? Figure 1 shows such a scenario, with Alice as the client, Bob as a daemon in the middle tier, and Charlie as a daemon on the back end. (The red boxes indicate the process token, and the red circle indicates the thread token.) Here's what happened to create this picture. When Alice called Bob, COM authenticated Alice and obtained a token for Alice for use on Bob's machine. (Technically, a pluggable security service provider does this on COM's behalf.) While servicing the call from Alice, Bob asked COM to place Alice's token on his thread by calling CoImpersonateClient. With his thread running as Alice, Bob then made a COM call to Charlie, intending to pass through Alice's identity in the hopes that Charlie would deal with access control. (This is very convenient for Bob!)
In this picture, COM has two choices for the call to Charlie. COM can choose to make the call as Alice or Bob because both the thread token (Alice) and the process token (Bob) are there for the picking. If COM were to take the natural course of action and choose the thread token, the call would fail miserably during the authentication handshake, at least under Windows NT 4.0. This is because the built-in authentication protocol (NTLM) doesn't support cross-host delegation of credentials. Under Windows 2000, there is a skosh of a chance that authentication might succeed if the system uses Kerberos, but there are still several knobs that need to be tweaked in the security database to make it work.
What it boils down to is that using the thread token isn't going to work in most cases, so COM simply ignores the fact that Bob's thread is impersonating Alice, and instead makes the call to Charlie using Bob's identity (which might surprise Bob, who was hoping Charlie would do access checking based on Alice's identity). On Windows NT 4.0, by default, outgoing COM calls use the process token because NTLM doesn't support more than one network hop with a client's credentials.
Access Control in Three-tier Systems
For most classic three-tier systems, a very effective way to perform access control is to do it as close to the client as possible while still being on a machine that isn't under the client's control. (In other words, it can't easily be compromised by a bad guy.) This generally means performing fine-grained access checks in the middle tier, which completely avoids the previous problem where I tried to delegate Alice's credentials. In fact, both COM+ and MTS have
built-in support for performing these access checks on a very fine-grained basis: per-class, per-interface, and in COM+, per-method. Performing access control in the middle tier has the benefit of offloading work from the back end, and increases the potential for sharing database connections from the middle tier to the back end.
With a Web application, Alice is making a call to Bob not via DCOM, but rather via HTTP, so IIS (not COM+) is the receptor of the request. IIS authenticates Alice and (typically) executes an ASP script on the middle-tier machine. Following this prescription for keeping access checks as close to the client as possible, it seems as though IIS should be tasked to perform access checks in this case. With that in mind, let's take a look at how IIS performs access control.
Access Control in IIS
IIS (both versions 4.0 and 5.0) has a very simple and consistent mechanism for dealing with access control. It simply passes the buck to whoever happens to be serving up the requested files. In other words, given an IIS thread servicing a request from Alice, that thread will simply impersonate Alice and attempt to open the requested file (or execute the requested ISAPI extension) while impersonating. The file system will complain if Alice hasn't been granted appropriate access permissions to the file in question. For anonymous clients, IIS impersonates a well-known account. (By default this is a local account called IUSR_MACHINE, where MACHINE is the computer name.) IIS actually prefers to use this anonymous account because it's less expensive than authenticating the client, and will do so unless IUSR_MACHINE doesn't have access permission to the file being requested, or if anonymous access has been disabled for the virtual directory in question. The important point to note is that IIS will always be impersonating someone when it processes an HTTP request.
To make IIS perform access control on your behalf, you simply customize the DACLs on each file the client might request. This is an incredibly coarse-grained solution, however. Clients will be either granted or denied access to the file as a whole. What if it's an ASP page, and you need to parse parameters in the URL to determine the client's intentions before you can perform any meaningful access checks? In this case, a strategy of simply placing DACLs on your ASP files is surely not going to cut it.
If the requested file is an ASP script and IIS is able to successfully open the script file while impersonating, the ASP ISAPI extension will carry on impersonating and execute the script. This gives you a chance to provide a more meaningful access control policy because IIS and ASP perform a handoff of the client's security information via the thread token, and you can do whatever sorts of access checks you like by peering into the thread token from ASP.
But let's be honest; that doesn't sound like a lot of fun. If you are using IIS and ASP as a gateway into COM+ (or MTS), then what you really want is another handoff, in this case from ASP to COM+. This will allow COM+ to perform its role-based access checks transparently, and will make you a much happier developer. Only one problem stands in the way. COM normally ignores the thread token in preference to the process token, which means that when you make a call from ASP to a local COM+ component, the client's identity seems to get dropped on the floor. Clearly the notion of using IIS as a gateway into COM+ breaks down if this happens. But before I describe the solution, I need to explain some infrastructure that's being added to COM in Windows 2000 to help address this issue.
Cloaking in Windows 2000
Windows 2000 is expected to provide a feature with an incredibly snazzy name (cloaking), but a really simple goal: to allow a COM client to specify that the thread token should not be ignored. By calling IClientSecurity::SetBlanket (or the shortcut, CoSetProxyBlanket), you can specify exactly how COM should use the thread token for subsequent calls through a particular proxy. Two mutually exclusive capability flags were added to COM in Windows 2000 to support this: EOAC_STATIC_CLOAKING and EOAC_DYNAMIC_CLOAKING.
To make use of this feature, call SetBlanket to specify one of these flags (via the dwCapabilities parameter). When you do, you'll be selecting one of three different policies that determines how COM figures out which credentials to use for subsequent outgoing calls on that particular proxy. To adjust the process-wide default policy, you can also pass these flags to CoInitializeSecurity so that the policy will automatically be applied to all proxies. (You can always call SetBlanket to override the default policy on a per-proxy basis, however.)
Here's what the individual policies mean. The first policy is in force if you don't pass either of these capability flags, and it says that COM will always ignore the thread token, just like in the old days.
The second policy is static cloaking, which says that at the time you call SetBlanket, the blanket captures the current identity of the thread and uses that identity for all subsequent calls through that proxy (until the next call to SetBlanket). For example, if a thread in Bob's process happens to be impersonating Alice, and that thread called SetBlanket specifying a policy of static cloaking, all future outgoing calls through that same physical interface pointer will always use Alice's identity. The identity was captured at the SetBlanket call (or on the first call through the proxy if the process-wide cloaking policy was used). This means that even if the thread stops impersonating Alice, calls through that proxy will still go out as Alice. To revert the proxy to use Bob's identity, the thread must call SetBlanket again once it has stopped impersonating.
The third policy is dynamic cloaking, which says that all subsequent outgoing calls through the proxy will be sensitive to the current security context of the thread making the call. So if a thread in Bob's process impersonates Alice and makes a call, the call goes out as Alice. If the thread then impersonates Mary and makes a call, the call goes out as Mary. If the thread stops impersonating and makes a call, the call goes out as Bob.
Interestingly enough, even in Windows NT 4.0 something akin to static cloaking has been available for some time now. (I verified by experimentation that this feature exists at least as far back as Service Pack 3.) The feature behaves exactly like static cloaking except for two subtle differences. First, to turn it on you don't pass EOAC_STATIC_CLOAKING. You simply pass NULL for the pAuthIdentity parameter. In other words, you don't provide explicit credentials to SetBlanket. Second, the feature only works if the proxy points to a remote object and the thread token that calls CoSetProxyBlanket is not a network token. If the proxy points to a local object, SetBlanket will succeed, but COM will still use the process token.
In Windows 2000, cloaking works with proxies to local and remote objects. Clearly you should be careful if you choose to take advantage of this feature in Windows NT 4.0 because the feature behaves (and is invoked) so differently on Windows 2000 that your program will likely break unless you check the operating system version at runtime and plan accordingly. The rule of thumb to avoid getting bitten is to simply not call SetBlanket while impersonating, unless you are willing to deal with the issues I just mentioned. This feature cannot be used to solve the IIS gateway problem (because it doesn't work locally), but it's an important part of the COM cloaking story, so I wanted to mention it here.
Default Cloaking Policy in COM+ Apps
In Windows 2000, COM+ server applications are loaded by a system-provided program known as DLLHOST.EXE, and therefore the settings you used to specify via your good friend CoInitializeSecurity (including the process-wide cloaking policy) are no longer set this way; rather, they are configured via the COM+ catalog, and COM+ applies them implicitly. For instance, the application-wide authentication level is one very obvious setting stored in the catalog. This setting is obvious because you can control it via the COM+ user interfacejust ask for an application's properties and select the Security tab. However, the cloaking policy, while stored in the catalog, is not exposed via the COM+ user interface, but can be read and written via the catalog's scripting interface.
The following script enumerates through the applications installed on the local machine, displaying the AuthenticationCapability setting for each application (which includes the cloaking policy):
Set cat = CreateObject("COMAdmin.COMAdminCatalog")
Set apps = cat.GetCollection("Applications")
For Each app In apps
caps = app.Value("AuthenticationCapability")
line = "0x" & Hex(caps) & Chr(9) & app.Name
Here are the results I received from Windows 2000 beta 3 (build 2072):
0x40 COM+ Utilities
0x40 COM+ IMDB Utilities
0x40 COM+ IMDB Proxy Connection Mgr
0x40 COM+ QC Dead Letter Queue Listener
0x40 IIS Utilities
0x40 IIS In-Process Applications
0x40 IIS Out-Of-Process Pooled Applications
0x40 Test App
0x2040 System Application
The dynamic cloaking bit is 0x40 and, as you can see, all the COM+ applications listed here have dynamic cloaking turned on by default. The application named Test App is one that I created from scratch to verify that this is indeed the default policy for COM+ applications. This directly affects ISAPI (and therefore ASP) applications.
Web Applications in Windows 2000
On Windows 2000, Web applications are hosted by an entity known as the Web Application Manager (WAM), which is simply a configured COM+ component that lives in an IIS-managed COM+ application. Take a look at the output from my script and you'll see a COM+ library application for hosting in-process Web apps and a server application for hosting pooled out-of-process Web apps. IIS itself (that is, INETINFO.EXE) and other ISAPI processes (these processes run under the guise of DLLHOST.EXE) naturally set up process-wide dynamic cloaking when calling CoInitializeSecurity.
Recall that the thread executing an ASP script is always impersonating either IUSR_MACHINE or the actual authenticated client, as I discussed earlier. This means that your ASP scripts will make outgoing COM calls using the original client's identity (via cloaking), and your COM+ components can therefore provide role-based access checks on your behalf. This is exactly what you want!
As a reminder, if you want to use this pass-through security model to hand off the client's identity to COM+, you'll want to co-locate your COM+ application on the same machine as the Web server receiving the request. (This configuration can of course be replicated on multiple middle-tier machines as you build a Web server farm.) If you try to call a remote object from ASP, you'll most likely be struck down immediately by the security Gods. Remember Figure 1; trying to use a client's credentials across two network hops isn't going to fly unless all the knobs are turned correctly to allow delegation of the client's credentials.
Once again I come back to the rule of keeping the access checks as close to the client as possible, and in this case it means performing access checks in the middle tier. Passing the buck (such as delegating security access checks) to the back tier is generally a bad practice, at least for classic three-tier Web-based applications, for the reasons I mentioned earlier. It usually won't work because of delegation issues, and even if it did, it would eliminate the benefit of database connection pooling in the middle tier.
Web Applications in Windows NT 4.0
The main difference back on Windows NT 4.0 is the lack of dynamic cloaking as a feature. IIS still works the same way; it impersonates its client and does its work while impersonating, thus passing the buck to the file system, and perhaps also to your ASP application that typically needs finer-grained access control. But when your ASP application makes a call through a proxy to a local COM object, as far as COM is concerned that call goes out using the process token, never using the thread token. Specifically, your COM object won't be able to use CoQueryClientBlanket or CoImpersonateClient to discover the identity of the client who made the HTTP request. If you try to discover the caller's identity this way, you'll see the process token from IISeither SYSTEM if your Web app runs in-process, or IWAM_MACHINE (where MACHINE is your computer name) if your Web app runs out-of-process. The original caller's identity gets dropped on the floor.
Or does it?
It turns out that MTS has an interesting feature that IIS uses to fix this difficult problem. (In all likelihood this feature was introduced to allow a smooth handoff from IIS to MTS.) Since the details of this feature are undocumented, I can only provide my own insights based on empirical evidence, and while I may not have all the details completely correct, the following description will help you gain a better understanding of security in your MTS-based Web applications.
When you first installed the Option Pack, you may have noticed a new alias (local group) with a mysterious name (MTS Impersonators) in User Manager. If your process token includes this alias, or if you are running in the System logon session (as a service, for instance), MTS works some interesting magic when threads in your process call IObjectContext::CreateInstance.
The first time an MTS object (A) calls IObjectContext::
CreateInstance (on B) while impersonating, the object context (at A) caches the thread token and all future method calls you make (from A) into local MTS server packages will appear to be using this cached identity, at least as far as MTS is concerned. COM will still report the calls coming in as using the process token, which makes it clear that MTS is doing some out-of-band work on your behalf to communicate the alternate identity. If you call ISecurityProperty::GetDirectCallerSID in a local MTS component being called from A, you'll see this behavior. Note that while COM and MTS disagree on the caller's identity, MTS performs all of its role-based access checks based on the MTS direct caller, not the COM caller. Hence, the overall effect is similar to the Windows 2000 behavior I described earlier (albeit less seamless).
Figure 2 MTS Magic
In the example depicted in Figure 2, the MTS component being called from ASP sees the following: CoQueryClientBlanket says that IWAM_2 is making the call, and ISecurityProperty::GetDirectCallerSID says that Alice is making the call. So the role-based access checks performed in Bob's process treat Alice as the caller, and all is well. This magic occurred because IWAM_2 is a member of the MTS Impersonators alias. (The Option Pack setup program automatically sets up the IWAM_MACHINE account this way, adding the MTS Impersonators alias if it wasn't already present.)|
Even though the MTS object context plays some caching gamesit captures the thread token the first time you call IObjectContext::CreateInstance and blindly uses that token for all subsequent outgoing calls from that objectthis scheme works quite well in ASP because (as far as I can tell) internally ASP creates a temporary MTS object representing the page. It uses this temporary object to process a single HTTP request, discarding it after the response is sent, and so the token is only cached for the duration of the requestwhich, by definition, can only come from a single client. If you call Server.CreateObject (or use a server-side object tag) from your ASP page to create an instance of a configured component, the effect is that all your method calls to that object (for the duration of the HTTP request) will be made on the client's behalf.
One common mistake that interferes with the handoff is calling vanilla CreateObject as opposed to Server.CreateObject. The former uses CoCreateInstance and the latter uses IObjectContext::CreateInstance, and in Windows NT 4.0 you must use the latter to get this magical handoff.
One way you can intentionally interfere with the handoff (if you've got an esoteric need to call into a local MTS package from ASP as SYSTEM or IWAM_MACHINE) is by writing explicit code to stop impersonating before you make your first call to Server.CreateObject. (RevertToSelf is the classic way to do this.) Recall that the object context doesn't cache the impersonation token until you make a call to Server.CreateObject while impersonating. After this happens, for the duration of your ASP script on the page all outgoing calls into any local MTS packages will use this cached token.
Experimenting with IIS and MTS Security
My sample code this month consists of a diagnostic COM component that you can call from various places to see what's going on under the covers when you make calls to COM+ and MTS components from various environments. (Figure 3 shows the interesting bits; you can download the rest from the link at the top of this article.)
To get a quick look at the way IIS and MTS perform their magic handoff, build and configure this component as an MTS server package, and set up a virtual directory (on the same machine) from which you can get to the following
<% Set w = Server.CreateObject("Whois.Who.1")
Response.Write w.WhoIsCaller() %>
This script simply creates an instance of the component excerpted in Figure 3, and calls a function asking the object to send back an HTML table showing the base COM and COM+/MTS callers. When used on Windows NT 4.0, this is quite enlightening.
Experiment by running this ASP script in-process as well as out-of-process. When running out-of-process, try removing the IWAM_MACHINE account from the MTS Impersonators group (be sure to unload your Web app if it was already running). Try putting a DACL on your ASP script that grants all access to Everyone, but no access to IUSR_MACHINE. As you experiment, you'll start to develop an intuition for how IIS and MTS security work. Go to http://www.develop.com/securitybriefs to see a summary of the results you should expect. (Do the experiments first though; no cheating!)
Using IIS as a gateway into COM+ (or MTS) is a very popular way to develop Web applications, and implementing security is quite easy if you simply pass through the client's identity to the local role-based security infrastructure provided by the platform. From ASP, remember to always call Server.CreateObject to make this magic work correctly on Windows NT 4.0at the very least, do this when instantiating configured componentsand keep your access checks as close to the client as possible to enhance the scalability of your application.
Your third tier should accept calls from the middle-tier daemon (I used Bob in my examples), and should trust Bob not to do evil things, thus eliminating costly fine-grained access checks on the back end, which is typically the bottleneck in a three-tier application.
On Windows NT 4.0, if you run an out-of-process Web application using an identity other than the default (IWAM_
MACHINE), be sure to add that account to the MTS Impersonators alias to preserve the magical handoff from IIS to MTS.
Thanks to Steve Rodgers (the light gray prince of DevelopMentor) for helping to independently verify the details of the MTS magic described in this column.