Code for this article: Feb99SecurityBriefs.exe (2KB)
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.
We're writing a distributed application that uses Windows NT® security features. We need to understand more about controlling access for anonymous users. Does a NULL session have anything to do with this? What about the Guest account? And while we're talking about anonymous users, what's the difference between Everyone and Authenticated Users?
Frans van der Geer and Xander Buffart
A The NULL session and the Guest account have been shrouded in mystery for some time. While various aspects have been documented in Knowledge Base articles, there has really been no formal documentation of what they mean and how they are used. It wasn't until Frans and Xander showed up as students in my "Essential Windows NT Security" class last year with a really bizarre COM security question that I sat down and dissected these issues.
Windows NT 4.0 uses a challenge-response protocol to establish a secure communication channel with a remote machine. To initiate the protocol, a remote client (Alice) sends a packet to the server (Bob). This packet contains a request to establish a secure channel, so Bob crufts up a random 64-bit number and sends it back to Alice (this is the challenge). Alice then must send a response, which includes her user name and some form of proof that she really is the person she claims to be. Since Alice's password is considered proof of her identity, the response takes the form of a 24-byte number that is computed as a function of Alice's password and the challenge.
When Alice sends this information back to Bob, he takes the challenge/response pair and hands it off to the Local Security Authority (LSA). In the case of NTLM authentication, this function takes the challenge/response pair as input, confirms Alice's identity, and returns a handle that references an executive object called an access token. If Alice is a local account, the LSA confirms Alice's identity by looking up the associated password and computing the response from the challenge. If Alice is a domain account, the LSA forwards the request to Alice's domain controller, which performs the confirmation. The key point to note is that for Alice to participate in this exchange successfully, she must know a secret (her password) to form the response.
Interestingly enough, Alice is not prompted for her password each time she connects to a remote file share, COM server, and so on. She simply logs in to Windows NT in the morning. Somehow the system remembers who she is, and throughout the day it is able to perform these authentication exchanges whenever they are necessary. Windows NT accomplishes this magic via the use of access tokens.
Access tokens are executive objects managed by the operating system, which cache information about a logon session for a particular user. Once produced by the LSA, a token can be used to verify a user's identity by any process on the machine where the token resides, without going through another authentication handshake. This means network authentication protocols such as NTLM are only required when hopping from one machine to another. Once Bob authenticates Alice and obtains a token from the LSA, he can use that token to acquire local resources on Alice's behalf via a mechanism known as impersonation. This mechanism does not require knowledge of Alice's password; it only requires that Alice allow her token to be used in this way. (For instance, the impersonation level in COM security allows Alice to control the way her token can be used during an authenticated call to Bob.)
Sometimes a logon session is interactive in nature, such as the one Alice initiates when she logs on to her workstation in the morning. Other times a logon session is created on behalf of a remote user, as in the earlier example when Bob obtains Alice's token. In the interactive case, where Alice provided her password to the system, enough information about her password is cached by the logon session to satisfy future password-based authentication requests. For the sake of this discussion, I'll refer to this cached information as an authenticator.
As an example, consider the case where Alice logs in to her workstation, launches a word processor, and tries to open a file on Bob's machine. Bob's file system driver challenges Alice's file system driver, which must then provide a response. Rather than popping up a dialog and asking for Alice's password again, the system simply looks at Alice's token to discover the logon session, and looks
in the logon session data structure for a cached authenticator to calculate the response. This is a very simplified view of the actual physical process; other forms of authenticators may also be cached for efficiency (for instance, Kerberos tickets).
Figure 1 Basic Anatomy of a Token
Figure 1 shows the elements of a token that are important for this discussion, including the reference to the cached authenticator. Once produced, the token provides two basic services: it holds the Security ID (SID) of the user that it represents and a cache of various information about the user that a program might need to access on a regular basis. This includes authorization information (groups and privileges) which is "flattened" for efficiency. By flattened I mean that when the first token for a particular logon session is produced, the groups and privileges that the user is a member of, either directly or indirectly, are listed once in the token.
So if Alice is a member of the Domain Admins group for her domain, and she logs into a machine where the local Administrators group includes the Domain Admins group, Alice's token will have both the Domain Admins SID as well as the local Administrators SID. Her token will then be populated with the union of all privileges assigned to these groups. This caching and flattening makes access control more efficient, but it also means that if someone changes Alice's membership in a group or the privileges she has on a particular machine, the change won't have any effect on existing logon sessions. In the interactive case, this means Alice will need to log out and log back in again to freshen her token if she wants to use any of the new groups or privileges she's been assigned. With Kerberos in Windows® 2000, logon sessions will have a limited lifetime (typically eight hours), at which time they will need to be refreshed.
Note that every token produced by the LSA contains a special well-known SID called Everyone. Granting access to Everyone effectively grants access to anyone for whom you can obtain some sort of token. The importance of this point will become evident later in this column.
Every user mode process that is created on Windows NT has a distinct token associated with it that identifies the user on whose behalf the process runs. So when Alice logs in, the system creates a token for her and starts the shell (typically explorer.exe) using this token. When Alice double-clicks to open her word processor, the shell receives this command and eventually calls CreateProcess to launch the program. CreateProcess simply duplicates the token of the calling process and creates a new process using this new token, which means the word processor now has a copy of Alice's token.
As a result, each new process does not need to prompt Alice for her password if it needs access to secure remote resources such as files. This is a good thing because these prompts would be incredibly annoying to users. Even worse, imagine how desensitized most users would become (in terms of disclosing their passwords) if they were prompted each time network authentication was necessary. It would be trivial to write a Trojan-horse program that spoofed the system and simply prompted Alice for her password. Caching an authenticator in protected kernel mode space is a reasonable solution to this problem for password-based systems.
Types of Logon Sessions
There are four basic types of logon sessions, and understanding the difference between them is the first step toward demystifying the concept of a NULL session (see Figure 2). Note that each logon type requires the security principal being logged on to have a particular privilege on the machine where the logon session is being generated.
As an example, for Alice to log on to her workstation interactively (by pressing Control+Alt+Delete and providing her credentials), the Alice account must have been granted the "Log on locally" privilege on her workstation (either directly or indirectly via group membership). For Alice to be authenticated when her word processor attempts to open a document on Bob's machine, Bob must grant Alice the right to a network-type logon at his machine because the file system will attempt a network-type logon.
By default, a network-type logon privilege is granted to Everyone, but you can see the incredible power it holds. If Bob takes this privilege away from Alice, the LSA will deny network-type logon for Alice, no matter what subsystem is requesting the session. This effectively prevents Alice from remotely accessing any file system shares, RPC servers, COM servers, and so on that Bob exposes from his machine.
Denying a user the right to obtain a network-type logon is rather harsh, so to give you finer-grained control, each token produced for a particular logon session will contain a group SID identifying exactly what type of logon session the token represents. You can then explicitly grant or deny access to that type of login on an object-by-object basis. As an example, run REGEDT32 or DCOMCNFG and check out any of the access permission editors. You'll see the following names listed: INTERACTIVE and NETWORK. If you're willing to do some security programming, you can also grant or deny access to batch and service-type logins, although this is generally not as meaningful or useful as using the locality of a user (interactive or network) to help determine your access control policy.
The LogonUser function allows your code to directly create a new logon session for a user, if you know the password for that account. If you'd like to experiment with the various types of logon sessions, temporarily grant yourself the "Act as part of the operating system" privilege and call LogonUser / ImpersonateLoggedOnUser and try to access remote file shares under various configurations. See Figure 3 for the sample code.
Notice that the network-type logon is the only logon session that does not have an authenticator. This makes sense, especially considering the heritage of Windows NT with the NTLM authentication protocol. When Alice connects to Bob's machine and is authenticated, the logon session produced by Bob's LSA cannot possibly have an authenticator for Alice, since Alice did not send her password across the wire. The NTLM response Alice sends to Bob is a cryptographic combination of the challenge and
a one-way hash of Alice's password; the password itself
is never sent across the wire. The NTLM response is effectively a one-shot that cannot be reused. (This is good
as it helps avoid replay attacks against the authentication protocol.)
In effect, code that runs under a token produced for a network-type logon session does not have an authenticator, and therefore has no meaningful way to answer a challenge. It cannot be authenticated across the network. So when Alice makes an authenticated COM call to Bob, Bob can use the network-type logon session for Alice to access local resources as Alice; however, he cannot use Alice's logon session to access secure resources on other machines. This restriction is expected to loosen in certain cases under Kerberos in Windows 2000, which explicitly supports delegation of credentials at the client's discretion.
What kind of code runs under a network-type logon session? Well, consider a COM server that runs As Activator (this is the default setting for COM servers; in DCOMCNFG this shows up on the Identity tab as Launching User). In this case, the COM SCM launches the server in response to a client's authenticated activation request. After authenticating the client and obtaining the client's token (using a network-type logon), the SCM starts a new process and assigns the client's token to the new process before it starts running. This process runs with a token that does not have an associated authenticator, which means that the process can access local resources, but it cannot access any remote resources where authentication is required (this includes most file system shares, COM servers, and so on).
Whenever code runs without an authenticator, as in the case just mentioned, it is said to have NULL network credentials. Processes often run with NULL network credentials. Services that run under the LocalSystem account on Windows NT 4.0 and earlier run with NULL network credentials. Processes that run under Windows 9x configured with share-level security (as opposed to user-level security) are considered to have NULL network credentials. These processes normally have to resort to alternate mechanisms to obtain an authenticator for use on the wire. For instance, a service running under the LocalSystem account has the privilege required to call LogonUser, and if an administrator configures an account for that service to use (and tells it the password via some configuration option), the service can establish a temporary logon session with an authenticator.
Without an authenticator, it is impossible for the system to establish a secure channel across the wire. Remember that the goal of establishing a secure channel is twofold. First, Bob would like to establish the identity of Alice. Second, Bob would like to establish a temporary session key with Alice that she can later use to assure him that she's still the one on the other end of the wire. Bob and Alice can also use the session key to encrypt or sign data that they send to one another. (As an example, consider the RPC and COM authentication level of PKT_PRIVACY.) With NULL network credentials, there is no way to establish this session key. But since there are lots of cases where allowing access to anonymous users is desirable, typically there
are mechanisms provided to allow a client with NULL network credentials to connect via what is known as a NULL session.
A NULL session is an insecure (unauthenticated) connection with no proof of identity. No session key is exchanged when establishing a NULL session, so it is impossible for the system to send encrypted or even signed messages on your behalf under a NULL session. When the LSA is asked to create a token for a remote client communicating via a NULL session, it produces a token with a user SID of S-1-5-7 (the NULL logon session), and a user name of ANONYMOUS LOGON. The token has the following pseudogroups:
Everyone is included in all tokens, and NETWORK indicates the type of logon.
Access to file system shares and named pipes are two important examples where NULL sessions can be used. If you check out Knowledge Base article Q124184 (which indicates certain file system-specific registry settings you can tweak), you'll see how to allow certain file system shares and named pipes to be accessed via a NULL session. If you don't explicitly configure these registry settings for a given share, clients with NULL sessions will be denied access by default. In this case, the client doesn't even make it past the authentication step, so it doesn't matter how you configure the access control settings on the share. (For example, granting access to Everyone on a file share doesn't automatically mean NULL sessions can access it.)
Independent of this file system-specific mechanism, if you use the CONNECT authentication level for a given COM or RPC call, NULL sessions are always allowed through the authentication check that COM performs. (I'll discuss access control shortly, which is a separate hurdle the NULL session must cross.) This is a reasonable behavior considering that CONNECT is really not very secure at all. At this level of authentication, any session key that is exchanged is never used, even to assure the server that the client's connection hasn't been hijacked; on any given call, the server really has no proof of who actually made the call. Since the session key is not used, you certainly don't need to exchange it in the first place, and therefore allowing NULL sessions is possible.
Controlling Access Rights for NULL Sessions
One cool thing about a NULL session is that a server can detect when a client is using one. Windows NT 4.0 Service Pack 3 has a new pseudogroup called Authenticated Users, which distinguishes a NULL session from all others. Clients who have been truly authenticated either interactively or via network authentication will have this pseudogroup in their token, while a token produced via a NULL session will not. Just as I demonstrated earlier with logon types, if you need finer-grained control, you can allow NULL sessions to access a particular share via the registry, then grant access on individual objects (files and directories) using Everyone or Authenticated Users, depending on whether you want NULL sessions to have access to a particular object. Just keep in mind that a token produced via a NULL session only has those two well-known groups inside it and you'll know how to deal with it safely.
Some folks with COM servers running with an authentication low-water mark of CONNECT on Windows NT 4.0 won't notice this behavior. Between two computers running Windows NT 4.0, COM prefers to use a connectionless protocol (UDP rather than TCP, say). Whenever you use a connectionless protocol, CONNECT is promoted transparently to PKT, which requires a session key. The session key is used to sign the RPC packet headers, which provide replay detection and verify that the connection has not been hijacked. So in these cases, clients using NULL sessions will be denied access when they attempt PKT-level authentication. However, in all other cases (between Windows 9x and Windows NT, for example) COM prefers TCP, and NULL sessions may slip in under the wire (this is not a bug, but many people aren't aware of this feature).
If you are using CONNECT as your low-water mark, seriously consider granting access permissions to Authenticated Users rather than Everyone to avoid anonymous clients slipping in undetected. This will become more important in Windows 2000 since the current story on the street is that connection-oriented protocols such as TCP will be preferred in all cases. I find it interesting that MTS packages (at least as of this writing) default to a low-water mark of PKT and thus avoid this issue entirely.
The Guest Account
Let's say Alice logs in to her workstation in the morning and sometime later she tries to access a share on Bob's machine. If Alice is using a domain account and Bob's domain doesn't trust Alice's domain, there is no way Alice can be authenticated, and normally Alice will be denied access to the share. A more common case where this occurs is when Alice tries to connect to Bob from home. (For simplicity, assume she's using her cable modem Internet connection.) Alice is running under a local account on her workstation named AliceAtHome, and Bob (as well as Bob's domain controller) knows nothing about this account. Alice will be denied access, naturally.
By enabling the Guest account on his machine, Bob can allow Alice to pass the authentication step successfully and be treated as a guest. To take a concrete example, in the second case I just described (assuming Bob has enabled the Guest account on his machine), the user ID in the token the LSA produces will be a well-known value on Bob's machine, one that ends with a special relative ID (RID) known as DOMAIN_USER_RID_GUEST. Interestingly, in this case the user name cached in the token will not be Guest, as
you might expect, but AliceAtHome. This is clearly just advisory information for Bob, who has no proof of the caller's identity.
Likewise, since no session key can be exchanged between Alice and Bob in this case, there is no way for Alice and Bob to send encrypted or signed messages. For file system operations this is not a problem, since the file system only verifies the caller's identity at connection time and doesn't normally require the use of a session key. For COM and RPC servers, this is somewhat more limiting and will only work if Alice makes calls to Bob at CONNECT-level authentication, where a session key is not necessary. (And recall that CONNECT is automatically promoted to PKT for connectionless transports.)
If you think the Guest account is a cool back door that you might be able to use, there are several gotchas you should be aware of. First, this back door will only work if the caller uses an account name that is completely unknown on the server's machine (or uses a domain account that is unknown to the domain or any domains it trusts). For instance, if Bob had a local account on his machine named AliceAtHome, with a password of foo, and Alice connected to Bob using AliceAtHome, but her password at home is bar, then Bob's LSA will not back off and use the Guest account. In this case, the LSA assumes that the caller is trying to spoof an existing account and will deny access to Alice.
Second, NULL sessions cannot use the Guest account back door, so a Windows NT service running under the LocalSystem account on Alice's machine will need to use some set of alternate credentials that Bob's machine doesn't know about (just come up with any set of bogus credentials) to be treated as Guest.
Third, the password for the Guest account can be: an empty string, or something else. An empty string is most flexible because a user with any password can be treated as Guest. Using a specific password for the Guest account means that the user's password must match the Guest account password for that user to be allowed under the Guest umbrella.
The final gotcha applies when you are writing security code that examines a Guest token. If you simply pull out the user ID from a Guest-style token and call LookupAccountSid, you'll be told that Guest is the user name. However, if you put the token on your thread (via impersonation) and call GetUserName, this will return the user name that was submitted during the (somewhat bogus) authentication exchange. This is why User Name is listed as a separate token element in Figure 1.
Be aware that on all Windows NT 4.0 installations, including Server and Workstation, the Guest account is disabled by default; you must enable it if you want to allow Guest logon sessions.
Controlling Access Rights for Guests
Here is a list of the groups Bob will find in the Guest token that the LSA created for AliceAtHome:
I've already described the first two groups. The Guests group is pretty obvious, and it's interesting that Users is also included. But what may be somewhat unintuitive is that Authenticated Users is included as well. By enabling the Guest account, you have effectively told the LSA to allow callers under the Guest umbrella to be treated as authenticated users. It's important that you keep this in mind when choosing an access control strategy.
Clearly, when the Guest account is enabled, you should consider using the Guests group to control access to resources, in addition to Authenticated Users, which (as I discussed earlier) is used to control access to NULL sessions.
Debugging Logon Sessions
Folks developing distributed systems using network authentication should seriously consider turning on auditing of success and failure of logon and logoff events while testing. You can do this by going to User Manager, selecting the Policies : Audit menu option, and turning on a couple of checkboxes. This will cause the LSA to record an event in the security event log whenever it either succeeds or fails to create a new logon session. This is handy when your COM clients (for instance) are unexpectedly getting E_ACCESSDENIED, or you are failing to access network resources such as files. The entry in the log looks something like this:
User Name: AliceAtHome
LogonID: (0x0, 0x30EB72D)
Logon Type: 3
Logon Process: KSecDD
Authn Package: MICROSOFT_AUTHN_PACKAGE_V1_0
Workstation Name: AlicesMachine
The numbers for the Logon Type field are simply the physical values for the LOGON32_LOGON_XXXX flags passed to LogonUser to create a session (these are also shown in Figure 2). The only discrepancy I've noticed is that the file system doesn't log an entry for a NULL session connection. Still, this is a handy tool.
For more information on these issues, visit my Web site at http://www.develop.com/kbrown. There you'll find some tables that show specific scenarios where NULL sessions and the Guest account play a role. Also check out the Knowledge Base (search MSDN for "NULL Session" and "Guest Account"). Article Q163632 is a particularly interesting article on the Guest account.
Have a question about security? Send it to Keith Brown at http://www.develop.com/kbrown.