Before you can assess application security in a Windows environment, you must understand the system's security features. You need to know how security is applied and how access to system resources is mediated. Having this knowledge enables you to identify what users can and can't access and how the OS decides what privileges users have. Therefore, this section introduces Windows sessions and the elements of access control that are referred to throughout this chapter and Chapter 12. Windows is a multiuser operating systemmeaning it can deal with multiple logged-on users simultaneously. Handling multiple simultaneous logons is accomplished by establishing sessions for each user who logs on successfully. A session is simply a mechanism for encapsulating data relevant to a logon instance. The data a session object maintains includes the following:
Sessions ensure that concurrently logged-on users can run applications more or less isolated from each other, thus preventing users from interfering with each other's processes to a certain extent. Session data structures and sessionwide accessible objects are explained later in this section. Note Keith Brown is the author of The .NET Developer's Guide to Windows Security (Addison-Wesley, 2005), which is an exceptional reference for the Windows security model. If you're more concerned with the lower-level API, you might want to consider his earlier book Programming Windows Security (Addison-Wesley, 2000). However the coverage centers on Windows NT and 2000, so some of the material is no longer current. Security IDsWindows access control mechanisms determine what access an entity has to a resource. An entity's identity is determined by the security ID (SID), a structure that contains a number of fields, including a revision level, an identifier authority value, a variable-length subauthority, and a relative ID (RID). SIDs are often represented in a text format, with each subfield broken out separately, like so: S-<revision>-<identifier authority>-<subauthority>-<RID> An example of a SID might look something like this: S-1-5-32-545 This SID identifies the well-known Users group. The 1 is the revision number, which has been the same for every version of Windows; the 5 is the authority ID of SECURITY_NT_AUTHORITY; the 32 is the subauthority for built-in accounts; and the 545 identifies the Users group. Note SIDs can be converted between text and structure form by using the ConvertStringSidToSid() and ConvertSidToStringSid() functions, respectively. For the purposes of this discussion, you can just think of a SID as a unique number that identifies an entity on the system, more commonly referred to as a "principal." A principal is any uniquely identifiable entity on the system that can be granted specific access to a system resource. Principals can be users, service accounts, groups, or machinesany entity associated with a logon session or a collection of these entities. You frequently encounter SIDs throughout the discussion of the Windows security model, because they play an essential role in determining who has access to what. The important thing to remember about SIDs is that account names can change over time and vary between languages, but a SID, after it's assigned, never changes. Further, the values of well-known SIDsaccounts guaranteed to exist on every system or domainnever change, either. Here are some examples of wellknown SIDs:
Logon RightsWindows logon rights aren't a session component but should be understood in the context of sessions. Logon rights determine whether a user can establish a logon session on a machine and what type of session is allowed. To view these rights, open the Local Security Policy Editor and navigate to Local Policies and then User Rights Assignment. Table 11-1 briefly summarizes these rights from the MSDN listing.
Access TokensAccess tokens are system objects that describe the security context for a process or thread. They are used to determine whether a process can or can't access a securable object or perform a system task that requires special privilege. Access tokens can be derived from a number of sources, but they are initially created when a user starts a new session. This initial token is referred to as a primary access token; it's assigned to all new processes started in the current logon session. The MSDN description for access tokens contains a list of components that make up the access token; the following list shows the main fields of interest:
A token containing all this information is created at every user logon and is later copied for each process and thread spawned in the session. Note that the token is copied, as opposed to a reference being passed, because each process or thread can optionally modify certain attributes of its access token. By using a copy for each process and thread, modifications don't affect other processes in the same session. Only certain parts of the access token can be modified by a process and a thread. Obviously, the unrestricted capability to change certain components of the token (such as the user and group SIDs or the privileges list) would completely undermine the security model. However, several other fields (such as the default DACL) can be modified safely to address access control concerns in a session. PrivilegesAs noted earlier, privileges are special permissions that allow a principal to perform system-related tasks. Table 11-2 lists privileges that can be granted to a principal.
Privileges play a vital role in system integrity; obviously, the haphazard assignment of privileges could result in a compromise of the system. For example, a user with SeDebugPrivilege can take over processes owned by other users; this privilege would allow attackers to run arbitrary code in the context of another account. Similarly, a user with SeLoadDriverPrivilege might load a malicious driver into kernel mode, thus taking complete control of the system. The default allocation of privileges is generally safe. However, services and similar applications might require additional access. If this access isn't carefully considered, it could create operational vulnerabilities that allow privilege escalation. Some applications must also downgrade permissions dynamically, and failing to do so might result in similar implementation vulnerabilities. This concern is addressed more later in the "Restricted Tokens" section. Group ListAn access token contains a list of SIDs for all the associated user's group memberships. When attempting to access an object, the object DACL is checked against entries in the group list. Access is refused if no matching entries exist or if an entry explicitly denies access. Otherwise, access is granted if a matching SID entry provides the requested level of access or higher. The SID list is generated at logon and can't be updated during a session. This approach allows performing access checks quickly and efficiently, even in a distributed environment. To see how this works, you can easily alter your account membership with the Microsoft Management Console. Any changes you make affect the account, but the current session is untouched. You have to log back on under a new session for changes in group membership to take effect. There's an exception to the requirement that group membership can't be altered for an active session. Group memberships can be somewhat altered through the use of SID attributes, which are parameters associated with each SID entry in the group list. They define how the SID entry applies and how it can be altered. So although new groups can't be added, existing groups can be altered by manipulating their attributes, and although groups can't be removed, any SID entry that isn't mandatory can be disabled. Table 11-3 describes attributes that can be associated with SIDs in a group list.
Restricted TokensFSome entries in a group list can be disabled, but even more extreme measures can be taken to reduce the permissions granted to a token. To do this, you create a restricted token, which is a token that has a nonempty restricted SID list. An access check for a restricted token differs from a normal token. An access check succeeds only if the DACL SID entry is present in both the normal group list and the restricted group list. Further, restricted tokens can set the SE_GROUP_USE_FOR_DENY_ONLY flag on mandatory SID entries. This approach can even be used to prevent the account from using its own SID for granting access to a resource. A restricted token can also revoke any privileges currently assigned to the token. By combining group and privilege restrictions, drastically limiting the access granted to a token object is possible. A restricted token is created by using the CreateRestrictedToken() function; its prototype is shown as follows: BOOL CreateRestrictedToken(HANDLE ExistingTokenHandle, DWORD Flags, DWORD DisableSidCount, PSID_AND_ATTRIBUTES SidsToDisable, DWORD DeletePrivilegeCount, PLUID_AND_ATTRIBUTES PrivilegesToDelete, DWORD RestrictedSidCount, PSID_AND_ATTRIBUTES SidsToRestrict, HANDLE NewTokenHandle) This function is used to supply a list of SIDs that can be disabled, to delete privileges from a token, and to add restricted SIDs to an access token. This effectively means that any process can create an access token containing a subset of the privileges and resource access rights the original token had. Of course, creating a new token might not be appropriate in many circumstances. Instead, you can modify attributes of the existing token with these functions: AdjustTokenGroups() and AdjustTokenPrivileges(). These functions can be used to alter an existing token by modifying group membership, as described in the section on group lists, or by altering token privileges. Here's the prototype of AdjustTokenGroups(): BOOL AdjustTokenGroups(HANDLE TokenHandle, BOOL ResetToDefault, PTOKEN_GROUPS NewState, DWORD BufferLength, PTOKEN_GROUPS PreviousState, PDWORD ReturnLength) This function can enable and disable groups in an access token, but the specified groups must already exist in the token's list of group SIDs. This function simply sets or clears the attributes discussed in the previous section. Primarily, it's used to set or clear the SE_GROUP_ENABLED attribute, which determines how the group affects an access check. A value of TRUE for the ResetToDefault parameter causes the NewState value to be ignored and the default state of the access token restored. Similarly, a process can enable or disable the privileges in an access token by using the AdjustTokenPrivileges() function. Here's the function prototype: BOOL AdjustTokenPrivileges(HANDLE TokenHandle, BOOL DisableAllPrivileges, PTOKEN_PRIVILEGES NewState, DWORD BufferLength, PTOKEN_PRIVILEGES PreviousState, PDWORD ReturnLength) Modifications made with AdjustTokenGroups() aren't irrevocable. Further, modifications made by using AdjustTokenPrivileges() are permanent only in Windows XP SP2 and Server 2003 or later and only if the SE_PRIVILEGE_REMOVED flag is set in the NewState parameter. This creates situations in which attackers can reset the token to its default state should they gain control of the process through a vulnerability. A restricted token, however, prevents the token from being reset to its original group list and privilege state. Software Restriction Policies (SAFER) APIWindows XP and Server 2003 added the Software Restriction Policies (SAFER) API to provide a simpler method of running processes under additional restrictions. The SaferCreateLevel() function provides machine and user scope restrictions and accepts five levels of security, ranging from disallowed to fully trusted. It can be used with SaferCreateTokenFromLevel() to create restricted tokens more easily. The SAFER levels from the MSDN are listed in Table 11-4.
Running Under Different ContextsWindows provides the capability to change the current thread's token or create a new process under a different token. Functionally, this capability is similar to the su command in UNIX. However, the implementation and use of the Windows functionality is very different. The first major difference is that Windows requires the user's password credentials to create a token for another user context. Note At first, requiring the user's password credentials to create a token for another user context might seem a bit odd. The local system account has unrestricted access to the account database and at some level eventually creates the logon session and token. Of course, this is true for a stand-alone system, and undocumented API calls could be used to manually generate a logon session and token for any user. However, Windows stand-alone authentication is more of a subset of Windows domain authentication. In a domain environment, only a domain controller has the context necessary to issue credentials for domain-level users. So a local system could use the native API calls to forge a domain token, but it would lack credentials needed for any network authentication. In the end, it seems the Windows designers chose to punt on this issue. They simply provide an API that always requires password credentials for authenticating a user. There are actually a few options for creating a process under a new user context. The first option works in Windows 2000 and later and is available to any authenticated user. It involves starting a process under a new user session by calling CreateProcessWithLogonW(). This function provides a programmatic interface to the Secondary Logon Service and is basically the same as shelling the RunAs command. The next option for creating a new user context uses the lower-level Win32 security function, LogonUser(). In Windows 2000 and earlier, this function requires the caller to have the SE_TCB_NAME privilege (described as the "act as part of the operating system" right); this right should be granted only to highly privileged accounts. This restriction severely limits the use of this function on earlier versions of Windows; it's useful only for providing external authentication in services that don't use native Windows IPC mechanisms. Windows provides seven different logon types, depending on how the token must be used. This distinction is important because it can improve performance and prevent an exposure of credentials. Table 11-5 lists the available logon types from the MSDN.
As you can see, each logon type performs slightly differently in handling credentials. For example, developers should use the LOGON32_LOGON_NETWORK type for a service that requires only authentication on the local system. Using another authentication mechanism in this situation, such as LOGON32_INTERACTIVE or LOGON32_NETWORK_PLAINTEXT, might cache sensitive user credentials unnecessarily. Attackers might then be able to steal credentials via an impersonation or Server Message Block (SMB) relay exploit. (Impersonation attacks are explained in more detail in Chapter 12.) After a token has been generated, it can be used to spawn another process by using CreateProcessAsUser() or CreateProcessWithTokenW(). Most user applications create a new token only when spawning a new process. However, a service might choose to replace credentials for the current thread by using SetThreadToken(), which brings you to a unique Windows capability known as impersonation. ImpersonationImpersonation is the capability for a thread running under one user session to use the credentials of another user session. It's done in two ways. The first method is to generate a token as described previously and assign that token to a thread with SetThreadToken(). This function requires that the caller have the SE_TOKEN_IMPERSONATE right on the target thread handle. The second, and more complex, form of impersonation is used in IPC in a client/server scenario. It's intended to allow the server process to duplicate (or impersonate) the client's credentials. This capability allows Windows systems to perform a single sign-on (SSO) on an individual system or across a domain environment. This capability is discussed in more detail in Chapter 12. |