Security Model
- Security Model
- Securing Objects
- SAM Database
- The Flow of a User Logon
- Summary
Security Model
In This Chapter
Securing Objects
Components
The Flow of a User Logon
In this chapter, I focus on the Windows 2000 internal components that provide security. First, I look at the schema Windows 2000 uses to protect objects, and then I look at the mechanisms that enforce those protections.
It's important to keep in mind where the objects I discuss actually livein user memory or (protected) kernel memory.
The answer, not surprisingly, is that these objects are located in a little bit of both spaces. Like the process, thread, and job objects you saw in Chapter 2, "Processes and Threads," both the kernel and the user-mode portion of the Win32 subsystem keep security information.
For the most part, the information kept in kernel space is the "real thing," and the user-mode structures are just used to pass information back and forth to the kernel. This is clear when you consider that security descriptors are attached to kernel objects, which live in kernel space, and tokens are kernel objects themselves. All of this will become clear in the following pages.
Securing Objects
At the heart of the Windows 2000 security model are Security Descriptors (SDs) and Access Control Lists (ACLs). Every securable object (files, devices, pipes, processes, threads, timers, printers, you name it) has a security descriptor attached to it. A security descriptor contains the following pieces of information:
The SID of the object owner
The SID of the primary owning group
Discretionary Access Control List (DACL)
System Access Control List (SACL)
WINNT.H, which is included in the Windows SDK, contains the SECURITY_DESCRIPTOR structure, as well as a brief explanation of the fields:
typedef struct _SECURITY_DESCRIPTOR { BYTE Revision; BYTE Sbz1; SECURITY_DESCRIPTOR_CONTROL Control; PSID Owner; PSID Group; PACL Sacl; PACL Dacl; } SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR; // Where: // // Revision - Contains the revision level of the security // descriptor. This allows this structure to be passed between // systems or stored on disk even though it is expected to // change in the future. // // Control - A set of flags that qualify the meaning of the // security descriptor or individual fields of the security // descriptor. // // Owner - A pointer to an SID representing an object's owner. // If this field is null, then no owner SID is present in the // security descriptor. If the security descriptor is in // self-relative form, then this field contains an offset to // the SID, rather than a pointer. // // Group - A pointer to an SID representing an object's primary // group. If this field is null, then no primary group SID is // present in the security descriptor. If the security descriptor // is in self-relative form, then this field contains an offset to // the SID, rather than a pointer. // // Sacl - A pointer to a system ACL. This field value is only // valid if the DaclPresent control flag is set. If the // SaclPresent flag is set and this field is null, then a null // ACL is specified. If the security descriptor is in // self-relative form, then this field contains an offset to // the ACL, rather than a pointer. // // Dacl - A pointer to a discretionary ACL. This field value is // only valid if the DaclPresent control flag is set. If the // DaclPresent flag is set and this field is null, then a null // ACL (unconditionally granting access) is specified. If the // security descriptor is in self-relative form, then this field // contains an offset to the ACL, rather than a pointer. //
The only thing I can add to this discussion is that the group SID is only used by POSIX applications.
NOTE
The POSIX standard requires that an object can be owned by a group. Folks familiar with UNIX will recognize this. Windows 2000, being POSIX-compliant, supports this notion, but it is not used in Win32 applications nor by the system.
Now that you're wondering what the heck an SID is, take a look.
SIDs
Ever feel as if you were just a number? Well, in Windows 2000, that's exactly what you are. Internally, Windows 2000 represents each account, group, machine, and domain with a security identifier, or SID. The SID is independent of the account name. You'll recall that if you delete an account, Windows displays the warning message shown in Figure 3.1.
Figure 3.1 Windows 2000 alludes to the existence of SIDs when you attempt to delete an account.
Here, I have assembled some pieces of WINNT.H so you can see how SIDs are defined:
// Pictorially the structure of an SID is as follows: // // 1 1 1 1 1 1 // 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // +---------------------------------------------------------------+ // | SubAuthorityCount |Reserved1 (SBZ)| Revision | // +---------------------------------------------------------------+ // | IdentifierAuthority[0] | // +---------------------------------------------------------------+ // | IdentifierAuthority[1] | // +---------------------------------------------------------------+ // | IdentifierAuthority[2] | // +---------------------------------------------------------------+ // | | // +- - - - - - - - SubAuthority[] - - - - - - - - -+ // | | // +---------------------------------------------------------------+ typedef struct _SID_IDENTIFIER_AUTHORITY { BYTE Value[6]; } SID_IDENTIFIER_AUTHORITY, *PSID_IDENTIFIER_AUTHORITY; typedef struct _SID { BYTE Revision; BYTE SubAuthorityCount; SID_IDENTIFIER_AUTHORITY IdentifierAuthority; #ifdef MIDL_PASS [size_is(SubAuthorityCount)] DWORD SubAuthority[*]; #else // MIDL_PASS DWORD SubAuthority[ANYSIZE_ARRAY]; #endif // MIDL_PASS } SID, *PISID; #endif
In English, an SID is a variable-length numeric structure that contains the following fields (right-to-left in the pictorial):
8-bit SID revision level
8-bit count of the number of subauthorities contained within
48 bits containing up to three identifier authority SIDs
Any number of subauthority SIDs and relative identifiers (RIDs)
Let's take apart an actual SID to make this more concrete. In regedt32, I look at an SID on my local machine in the HKEY_USERS subtree (see Figure 3.2). The name of the subtree is the user's SID.
Figure 3.2 The HKEY_USERS Registry subtree contains a key for each local user.
You can see the textual representation of my SID is
S-1-5-21-1960408961-1708537768-1060284298-1000
I say textual representation because the S prefix and hyphens separating the fields are added to make SIDs more readable. The true internal representation is just a bunch of numbers all run together.
The SID has a revision level of 1. You can see from WINNT.H that Windows 2000's current revision level is 1:
#define SID_REVISION (1) // Current revision level
What follows are three identifier authorities: namely 5 and 21. There are several built-in authorities, as shown in Table 3.1.
Table 3.1 Built-in Identifier Authorities
Identifier Authority |
Number |
SECURITY_NULL_SID_AUTHORITY |
0 |
SECURITY_WORLD_SID_AUTHORITY |
1 |
SECURITY_LOCAL_SID_AUTHORITY |
2 |
SECURITY_CREATOR_SID_AUTHORITY |
3 |
SECURITY_NT_AUTHORITY |
5 |
Back to the SID, 5 means that this SID was assigned by the Windows 2000 security authority (as most are), and 21 means simply that this is not a built-in SID. Windows 2000 calls these non-unique, which means that a relative identifier (RID) is required to make the SID unique. You'll see this in just a second.
The three subauthority values identify the local machine and the domain (if any) the machine belongs to. In the example, all accounts on my machine share the same three subauthorities.
At setup, Windows 2000 creates a random-base SID based on a number of items, including the current date and time and Ethernet address (if available). Windows goes to great pains to make sure this is a globally unique identifier. It is very unlikely that any machine anywhere in the world has the same base SID.
The last chunk, namely the 1000, is the RID. This number is tacked on to the end of the machine SID to create unique SIDs for users and groups. Windows starts numbering at 1,000, so this account is the first account created on this machine. The next account or group created would have the RID 1001, and so on. Voila, we have a unique identifier.
There are a number of predefined (or built-in) SIDs that serve special roles within Windows. Again, WINNT.H serves as the reference here:
///////////////////////////////////////////////////////////////////////////// // // // Universal well-known SIDs // // // // Null SID S-1-0-0 // // World S-1-1-0 // // Local S-1-2-0 // // Creator Owner ID S-1-3-0 // // Creator Group ID S-1-3-1 // // Creator Owner Server ID S-1-3-2 // // Creator Group Server ID S-1-3-3 // // // // (Non-unique IDs) S-1-4 // // // ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // // // NT well-known SIDs // // // // NT Authority S-1-5 // // Dialup S-1-5-1 // // // // Network S-1-5-2 // // Batch S-1-5-3 // // Interactive S-1-5-4 // // Service S-1-5-6 // // AnonymousLogon S-1-5-7 (aka null logon session) // // Proxy S-1-5-8 // // ServerLogon S-1-5-9 (aka domain controller account) // // Self S-1-5-10 (self RID) // // Authenticated User S-1-5-11 (Authenticated user somewhere) // // Restricted Code S-1-5-12 (Running restricted code) // // // // (Logon IDs) S-1-5-5-X-Y // // // // (NT non-unique IDs) S-1-5-0x15-... // // // // (Built-in domain) s-1-5-0x20 // // // /////////////////////////////////////////////////////////////////////////////
You'll notice the NT non-unique IDs) S-1-5-0x15-... reference. That matches the SID I discuss as hexadecimal 15 = 21. So an S-1-5-21-... SID is an NT non-unique SID that receives a RID to make it unique. This schema describes all user and group accounts created by the administrator.
Similarly, there are built-in RIDs that you must recognize. They are usually referred to as "well-known" SIDs or RIDs because the SID can easily be determined. The nice source commenting in WINNT.H you saw earlier breaks down at this part, so I created some tables to illustrate. Table 3.2 identifies the well-known user RIDs.
Table 3.2 Well-Known User RIDs
Name |
RID |
DOMAIN_USER_RID_ADMIN |
500 |
DOMAIN_USER_RID_GUEST |
501 |
DOMAIN_USER_RID_KRBTGT |
502 |
That means that on my machine, the SID of the local administrator account is
S-1-5-21-1960408961-1708537768-1060284298-500
The SIDs of the guest account and Kerberos TGT are also well known. Table 3.3 identifies the well-known group RIDs.
Table 3.3 Well-Known Group RIDs
Name |
RID |
DOMAIN_GROUP_RID_ADMINS |
512 |
DOMAIN_GROUP_RID_USERS |
513 |
DOMAIN_GROUP_RID_GUESTS |
514 |
DOMAIN_GROUP_RID_COMPUTERS |
515 |
DOMAIN_GROUP_RID_CONTROLLERS |
516 |
DOMAIN_GROUP_RID_CERT_ADMINS |
517 |
DOMAIN_GROUP_RID_SCHEMA_ADMINS |
518 |
DOMAIN_GROUP_RID_ENTERPRISE_ADMINS |
519 |
You see the same idea here. The SID of my local administrators group is
S-1-5-21-1960408961-1708537768-1060284298-512
Table 3.4 identifies the well-known alias RIDs.
Table 3.4 Well-Known Alias RIDs
Name |
RID |
DOMAIN_ALIAS_RID_ADMINS |
544 |
DOMAIN_ALIAS_RID_USERS |
545 |
DOMAIN_ALIAS_RID_GUESTS |
546 |
DOMAIN_ALIAS_RID_POWER_USERS |
547 |
DOMAIN_ALIAS_RID_ACCOUNT_OPS |
548 |
DOMAIN_ALIAS_RID_SYSTEM_OPS |
549 |
DOMAIN_ALIAS_RID_PRINT_OPS |
550 |
DOMAIN_ALIAS_RID_BACKUP_OPS |
551 |
DOMAIN_ALIAS_RID_REPLICATOR |
552 |
DOMAIN_ALIAS_RID_RAS_SERVERS |
553 |
Here you see the aliases that any NT administrator is familiar with. These too are well-known SIDs. Table 3.5 outlines miscellaneous reserved RIDs.
Table 3.5 Miscellaneous reserved RIDs
Name |
RID |
RESERVED (previous tables) |
0997 |
ANONYMOUS_LOGON_LUID |
998 |
SYSTEM_LUID |
999 |
First user/group |
1000 |
Second user/group |
1001 |
...and so on |
100n |
NOTE
Are these well-known SIDs a security risk? Well, the answer is "It depends." If your machine has NetBIOS open to the outside world, then yes, it is a risk. It is trivial for a cracker to enumerate the account names and SIDs and thus find the real administrator account name, even if it was renamed (which some security checklists recommend). However, if NetBIOS is blocked to the outside world (as it should be), the SIDs aren't accessible and there is no risk.
Now that you're comfortable with SIDs, take a look at the other cornerstone of Windows 2000 security: Access Control Lists.
Access Control Lists (ACLs)
Access Control Lists (ACLs) contain the actual permissions assigned to an object, as well as audit instructions for the kernel. An ACL consists of a header followed by zero or more Access Control Entries (ACEs). An ACL with zero ACEs is called a null ACL.
There are two types of ACLs: discretionary ACLs (DACLs) and system ACLs (SACLs). DACLs define access permissions to the object they protect, whereas SACLs contain audit instructions for the system.
Again, turn to WINNT.H for the definition of an ACL:
// Define an ACL and the ACE format. The structure of an ACL header // followed by one or more ACEs. Pictorially the structure of an ACL header // is as follows: // // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // +-------------------------------+---------------+---------------+ // | AclSize | Sbz1 | AclRevision | // +-------------------------------+---------------+---------------+ // | Sbz2 | AceCount | // +-------------------------------+-------------------------------+ // // The current AclRevision is defined to be ACL_REVISION. // // AclSize is the size, in bytes, allocated for the ACL. This includes // the ACL header, ACES, and remaining free space in the buffer. // // AceCount is the number of ACES in the ACL. typedef struct _ACL { BYTE AclRevision; BYTE Sbz1; WORD AclSize; WORD AceCount; WORD Sbz2; } ACL; typedef ACL *PACL;
Similarly, the definition of an ACE follows:
// The structure of an ACE is a common ace header followed by ace type // specific data. Pictorially the structure of the common ace header is // as follows: // // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // +---------------+-------+-------+---------------+---------------+ // | AceSize | AceFlags | AceType | // +---------------+-------+-------+---------------+---------------+ // // AceType denotes the type of the ace; there are some predefined ace // types // // AceSize is the size, in bytes, of ace. // // AceFlags are the Ace flags for audit and inheritance, defined shortly. typedef struct _ACE_HEADER { BYTE AceType; BYTE AceFlags; WORD AceSize; } ACE_HEADER; typedef ACE_HEADER *PACE_HEADER;
Notice that in the ACL header, the size field is defined to contain the size of the ACL header plus ACEs plus any extra space. A whole ACL looks like Figure 3.3.
Figure 3.3 A complete ACL.
Currently, there are four defined ACE structures, outlined in Table 3.6. The type of ACE determines whether the ACL is system (SACL) or discretionary (DACL).
Table 3.6 Currently Defined ACE Types
Type |
Makes ACL type |
ACCESS_ALLOWED_ACE |
Discretionary (DACL) |
ACCESS_DENIED_ACE |
Discretionary (DACL) |
SYSTEM_ALARM_ACE |
System (SACL) |
SYSTEM_AUDIT_ACE |
System (SACL) |
Even though this mysterious SYSTEM_ALARM_ACE type is defined, it is not yet supported in Windows 2000.
Let's look at the ACCESS_ALLOWED_ACE type:
typedef struct _ACCESS_ALLOWED_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD SidStart; } ACCESS_ALLOWED_ACE
You see here that an ACCESS_ALLOWED_ACE contains the generic ACE header, which you just saw, as well as an ACCESS_MASK and an SID.
You can guess the story here: The existence of an ACCESS_ALLOWED_ACE grants the access specified by the ACCESS_MASK to the user or group identified by the SID. Similarly, the existence of a SYSTEM_AUDIT_ACE causes the system to log an event to the security audit log when the user or group specified by the SID requests the access specified in the ACCESS_MASK. Pretty straightforward. You can imagine that the ACCESS_DENIED_ACE and the eventual ACCESS_ALARM_ACE ACEs do pretty much the same thing.
For a discussion of access masks, turn to WINNT.H:
// Define the access mask as a longword sized structure divided up as // follows:// // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // +---------------+---------------+-------------------------------+ // |G|G|G|G|Res'd|A| StandardRights| SpecificRights | // |R|W|E|A| |S| | | // +-+-------------+---------------+-------------------------------+ // // typedef struct _ACCESS_MASK { // WORD SpecificRights; // BYTE StandardRights; // BYTE AccessSystemAcl : 1; // BYTE Reserved : 3; // BYTE GenericAll : 1; // BYTE GenericExecute : 1; // BYTE GenericWrite : 1; // BYTE GenericRead : 1; // } ACCESS_MASK; // typedef ACCESS_MASK *PACCESS_MASK; // // But to make life simple for programmers we'll allow them to specify // a desired access mask by simply OR'ing together multiple single rights // and treat an access mask as a DWORD. For example // // DesiredAccess = DELETE | READ_CONTROL // // So we'll declare ACCESS_MASK as DWORD typedef DWORD ACCESS_MASK; typedef ACCESS_MASK *PACCESS_MASK;
This is the overall structure of the access mask. You can see that there are bits for generic read, write, execute, and all privileges. It is also possible to specify specific rights that vary depending on the object type. For example, printers have different rights (clear queue, change paper to tray assignments, and so on) than files do. The specific rights are defined on a per-object-type basis. Programmers should consult the SDK for information on specific objects or on how to create specific rights for your objects.
I have some special cases to address for completeness. If an object's SD contains no DACL, then everyone has full access to the object. This is important to keep in mind. On the other hand, if the DACL is null (contains 0 ACEs), then no one has access to the object (except the owner, as you'll see in the next section).
If the SACL is null (or contains no ACEs), then no auditing occurs.
Assigning and Inheriting ACLs and ACEs
The algorithm for assigning ACLs to new objects follows:
If the caller explicitly provides an SD when creating the object, that SD is used if possible (as long as the caller has sufficient rights).
Any ACEs marked for mandatory inheritance in the object hierarchy above our new object are included.
If the caller didn't specify an SD, the object manager searches for ACEs marked as inheritable in the object hierarchy above our new object and includes those.
If none of the preceding steps applies, the default ACL from the caller's token is applied. This is the most common occurrence.
Windows 2000 contains some significant improvements over previous versions of Windows NT when it comes to ACL inheritance. In previous versions, ACEs could only be inherited at object creation or when a new ACL was explicitly set on an existing object. The system did not propagate inheritable ACEs to child objects. Moreover, the system did not differentiate between inherited and directly applied ACEsso an object was unable to protect itself from inherited ACEs.
In Windows 2000, the SetNamedSecurityInfoEx() and SetSecurityInfoEx() functions support automatic propagation of inheritable ACEs. For example, if you use these functions to add an inheritable ACE to a directory in an NTFS volume, the system applies the ACE as appropriate to the ACLs of any subdirectories and files.
Win2K also introduces a new inheritance model where directly assigned ACEs have precedence over inherited ACEs. This is accomplished by adjusting the order of the ACEs in the ACL. To ensure that non-inherited ACEs have priority over inherited ACEs, all non-inherited ACEs precede all inherited ACEs. This ordering ensures, for example, that a non-inherited access-denied ACE is enforced regardless of any inherited ACE that allows access.
With the new inheritance model comes a few additional particulars:
If a child object with no DACL inherits an ACE, the result is a child object with a DACL containing only the inherited ACE.
If a child object with an empty DACL inherits an ACE, the result is a child object with a DACL containing only the inherited ACE.
If you remove an inheritable ACE from a parent object, automatic inheritance removes any copies of the ACE inherited by child objects.
If automatic inheritance results in the removal of all ACEs from a child object's DACL, the child object has an empty DACL rather than no DACL.
These rules can have the unexpected result of converting an object with no DACL to an object with an empty DACL. You'll recall that there is a big difference here: An object with no DACL allows full access, but an object with an empty DACL allows no access. To ensure that inheritable ACEs do not affect a child object with no DACL, set the SE_DACL_PROTECTED flag in the object's security descriptor.
SD/ACL/ACE Summary
Summing up, Figure 3.4 shows a few hypothetical objects, their security, and the effects on the system.
Figure 3.4 Some ACLs containing useful ACEs.
Tokens
If security descriptors and ACLs are the locks, then tokens are the keys. Each process and thread has a token, which contains the SID of the process's owner as well as the SIDs of the groups to which they belong.
The system uses an access token to identify the user when a thread tries to obtain a handle to a securable object or tries to perform a privileged system task. Access tokens contain the following information:
The SID of the logon account
SIDs for the groups the account is a member of
A logon SID that identifies the current logon session
A list of the privileges held by either the user or the user's groups
The SID for the primary group
The default DACL that the system uses when the user creates a securable object without specifying a security descriptor
The source of the access token
Whether the token is a primary or impersonation token
An optional list of restricting SIDs
Current impersonation levels
Other statistics
The kernel's security reference monitor (covered later in this chapter) compares the SID in the token with the SIDS in the ACLs to determine whether access will be permitted, whether auditing will be performed, and so on.
Let's look at a token. Instead of looking at the Win32 structures, I use the kernel debugger's !tokenfields command. The Win32 structures are similar, but for the illustration the !tokenfields output is more concise. Remember, the values shown are the offsets to the fields, not actual values. This is just what the kernel structure looks like:
kd> !tokenfields TOKEN structure offsets: TokenSource: 0x0 AuthenticationId: 0x18 ExpirationTime: 0x28 ModifiedId: 0x30 UserAndGroupCount: 0x3c PrivilegeCount: 0x44 VariableLength: 0x48 DynamicCharged: 0x4c DynamicAvailable: 0x50 DefaultOwnerIndex: 0x54 DefaultDacl: 0x6c TokenType: 0x70 ImpersonationLevel: 0x74 TokenFlags: 0x78 TokenInUse: 0x79 ProxyData: 0x7c AuditData: 0x80 VariablePart: 0x84 kd>
In the token you find all the fields you expect plus a few additional fields for housekeeping purposes.
Using the kernel debugger, let's take apart the token associated with the WINWORD.EXE process I am currently using to write this book:
PROCESS fdd4b020 Cid: 02a0 Peb: 7ffdf000 ParentCid: 0280 DirBase: 02023000 ObjectTable: fdd24a48 TableSize: 98. Image: winword.exe VadRoot fdd2b388 Clone 0 Private 348. Modified 3. Locked 0. DeviceMap fdf14128 Token e1eec030 ElapsedTime 0:02:04.0539 UserTime 0:00:00.0270 KernelTime 0:00:00.0600 QuotaPoolUsage[PagedPool] 37828 QuotaPoolUsage[NonPagedPool] 5540 Working Set Sizes (now,min,max) (1325, 50, 345) (5300KB, 200KB, 1380KB) PeakWorkingSetSize 1325 VirtualSize 40 Mb PeakVirtualSize 46 Mb PageFaultCount 1500 MemoryPriority FOREGROUND BasePriority 8 CommitCharge 425
Looking at the process object, you see the token is stored at 0xe1eec030. Using the !token extension to elaborate
kd> !token e1eec030 TOKEN e1eec030 Flags: 9 Source User32 \ AuthentId (0, 5ceb) Type: Primary (IN USE) Token ID: 127b6 ParentToken ID: 0 Modified ID: (0, a6a8) TokenFlags: 0x9 SidCount: 10 Sids: e1eec180 RestrictedSidCount: 0 RestrictedSids: 0 PrivilegeCount: 17 Privileges: e1eec0b4
Here is the actual token and its values. This is a primary token because the WinWord process isn't impersonating anyone. (I get to impersonation in a moment.)
Most interesting are the SIDs, which I explore here. First, I display the dwords (with the kernel debugger dd command) starting at the memory location specified in the token, namely 0xe1eec180:
kd> dd e1eec180 e1eec180 e1eec1d0 00000000 e1eec1ec 00000007 e1eec190 e1eec208 00000007 e1eec214 0000000f e1eec1a0 e1eec224 00000007 e1eec234 00000007 e1eec1b0 e1eec244 c0000007 e1eec258 00000007 e1eec1c0 e1eec264 00000007 e1eec270 00000007 e1eec1d0 00000501 05000000 00000015 74d97781 e1eec1e0 65d637a8 3f32a78a 000003e8 00000501 e1eec1f0 05000000 00000015 74d97781 65d637a8
Taking note of the SidCount = 10 in the token, I display the 10 SIDs using the !sid command:
kd> !sid e1eec1d0 SID is: S-1-5-21-1960408961-1708537768-1060284298-1000 kd> !sid e1eec1ec SID is: S-1-5-21-1960408961-1708537768-1060284298-513 kd> !sid e1eec208 SID is: S-1-1-0 kd> !sid e1eec214 SID is: S-1-5-32-544 kd> !sid e1eec224 SID is: S-1-5-32-547 kd> !sid e1eec234 SID is: S-1-5-32-545 kd> !sid e1eec244 SID is: S-1-5-5-0-23483 kd> !sid e1eec258 SID is: S-1-2-0 kd> !sid e1eec264 SID is: S-1-5-4 kd> !sid e1eec270 SID is: S-1-5-11
Great. You see a list of SIDs that are contained in this token. Boy, there sure seem to be a lot. Table 3.7 lists the SIDs with some descriptions.
Table 3.7 SIDs Included in Jeff's Word Token
SID |
Description |
S-1-5-21-1960408961-1708537768-1060284298-1000 |
My user account SID as seen in the previous SID discussion. |
S-1-5-21-1960408961-1708537768-1060284298-513 |
Local users group. |
S-1-1-0 |
World (everyone). Every token has this SID. |
S-1-5-32-544 |
Local built-in domain administrators group. |
S-1-5-32-547 |
Local built-in power users group. |
S-1-5-32-545 |
Local built-in users group. |
S-1-5-5-0-23483 |
Logon ID (see logon, later in this chapter, for more info) |
S-1-2-0 |
Localthis is a local logon. |
S-1-5-4 |
InteractiveI am at the console. |
S-1-5-11 |
Authenticated userall non-anonymous users (see Note). |
NOTE
The Authenticated Users group was added in a patch to Windows NT 4.0 as a result of the "red button" vulnerability.
Figure 3.5 shows that I am a member of the local administrators group.
My membership in the power users group comes from the fact that authenticated users is defined as a member of power users on a Windows 2000 Professional machine, as mine is. This is shown in Figure 3.6. The net effect is that all non-anonymous users (that is, authenticated users) are also power users.
The SIDs in the token you examined are my keys to the system. As I access resources, the security reference monitor makes sure that the security descriptor (and contained DACLs) attached to each resource permit one of the SIDs listed in my token to execute whatever action I am requesting. Similarly, if there is an SACL contained within the security descriptor, the system checks whether auditing is necessary given my list of SIDs and the access I am requesting. Pretty cool, eh?
Figure 3.5 I am a member of the local administrators group, as the SIDs suggest.
Figure 3.6 The group authenticated users is a member of the group power users.
Privileges and User Rights
I sidetrack here for just a moment and talk about privileges. Privileges, unlike access rights, affect the system as a whole, whereas access rights affect only a certain securable object. As an example, the ability to read the file c:\hi.txt is an access rightthe type of thing I've been talking about thus far. However, shutting down the system, for example, is a privilege because its scope is not limited to just one object.
Table 3.8 comes directly from the SDK and gives a brief description of each privilege.
Table 3.8 Common Privileges
Privilege |
Description |
SE_ASSIGNPRIMARYTOKEN_NAME |
Required to assign the primary token of a process. |
SE_AUDIT_NAME |
Required to generate audit-log entries. Give this privilege to secure servers. |
SE_BACKUP_NAME |
Required to perform backup operations. |
SE_CHANGE_NOTIFY_NAME |
Required to receive notifications of changes to files or directories. This privilege also causes the system to skip all traversal access checks. It is enabled by default for all users. |
SE_CREATE_PAGEFILE_NAME |
Required to create a paging file. |
SE_CREATE_PERMANENT_NAME |
Required to create a permanent object. |
SE_CREATE_TOKEN_NAME |
Required to create a primary token. |
SE_DEBUG_NAME |
Required to debug a process. |
SE_INC_BASE_PRIORITY_NAME |
Required to increase the base priority of a process. |
SE_INCREASE_QUOTA_NAME |
Required to increase the quota assigned to a process. |
SE_LOAD_DRIVER_NAME |
Required to load or unload a device driver. |
SE_LOCK_MEMORY_NAME | Required to lock physical pages in memory. |
SE_PROF_SINGLE_PROCESS_NAME |
Required to gather profiling information for a single process. |
SE_REMOTE_SHUTDOWN_NAME |
Required to shut down a system using a network request. |
SE_RESTORE_NAME |
Required to perform restore operations. This privilege enables you to set any valid user or group SID as the owner of an object. |
SE_SECURITY_NAME |
Required to perform a number of security-related functions, such as controlling and viewing audit messages. This privilege identifies its holder as a security operator. |
SE_SHUTDOWN_NAME |
Required to shut down a local system. |
SE_SYSTEM_ENVIRONMENT_NAME |
Required to modify the non-volatile RAM of systems that use this type of memory to store configuration information. |
SE_SYSTEM_PROFILE_NAME |
Required to gather profiling information for the entire system. |
SE_SYSTEMTIME_NAME |
Required to modify the system time. |
SE_TAKE_OWNERSHIP_NAME |
Required to take ownership of an object without being granted discretionary access. This privilege allows the owner value to be set only to those values that the holder may legitimately assign as the owner of an object. |
SE_TCB_NAME |
This privilege identifies its holder as part of the trusted computer base. Some trusted protected subsystems are granted this privilege. This privilege is required to call the LogonUser function. |
SE_UNSOLICITED_INPUT_NAME |
Required to read unsolicited input from a terminal device. |
SE_MACHINE_ACCOUNT_NAME |
Required to create a machine account. |
Many of these should be familiar to NT administrators as the "user rights" seen in User Manager, and now in the MMC, as in Figure 3.7. Windows 2000 gives administrators GUI access to most of these privileges.
Figure 3.7 User rights and privileges are synonymous.
Taking a look at the WINWORD.EXE processthis time using the PVIEW.EXE tool that ships with the Windows NT 4 Resource Kityou see the privileges that are enabled and the ones that are disabled (see Figure 3.8). Note that the enabled groups are the same as you saw in the exercise earlier. (Scrolling down reveals the rest of them.)
Figure 3.8 The PVIEW.EXE tool in the Windows NT 4 Resource Kit shows more token information.
NOTE
Yes, I am using a tool from the Windows NT 4.0 Resource Kit to explore my Windows 2000 machine. The PVIEW.EXE tool is not included in the pre-release build of the Windows 2000 Resource Kit that I have. Because the structure of the token has not changed between NT 4 and Windows 2000, the NT 4 Resource Kit does the trick in this case. However, be careful when mixing versions of system-level tools.
Some privileges are very powerful, so Microsoft put a two-tier mechanism in place for using the rights associated with privileges. When attempting a privileged action, not only must the privileges be held by the client, but they must also be enabled.
Say I am attempting to call the LogonUser() Win32 function, which requires the SE_TCB_NAME privilege. My administrator granted my account (or a group to which I belong) the SE_TCB_NAME privilege using User Manager or another tool. However, this privilege (and all others) are disabled by default. Thus, my program does the following:
Call OpenThreadToken() to get a handle to my primary (or impersonation) token.
Call AdjustTokenPrivileges() to enable the necessary privileges, in this case SE_TCB_NAME.
Do the call to LogonUser().
Call AdjustTokenPrivileges() to disable the privilege.
SYSTEM Context
Many system processes run under a special access token called SYSTEM. Analyzing the SYSTEM token as I did my own token earlier, you see the following:
kd> !token e10007d0 TOKEN e10007d0 Flags: 9 Source *SYSTEM* AuthentId (0, 3e7) Type: Primary (IN USE) Token ID: 3ea ParentToken ID: 0 Modified ID: (0, 3e9) TokenFlags: 0x9 SidCount: 4 Sids: e1000950 RestrictedSidCount: 0 RestrictedSids: 0 PrivilegeCount: 21 Privileges: e1000854 kd> dd e1000950 e1000950 e1000970 00000000 e100097c 0000000e e1000960 e100098c 00000007 e1000998 00000007 ... kd> !sid e1000970 SID is: S-1-5-18 kd> !sid e100097c SID is: S-1-5-32-544 kd> !sid e100098c SID is: S-1-1-0 kd> !sid e1000998
Table 3.9 lists the SIDs that are contained within the SYSTEM token.
Table 3.9 SIDs Contained in the SYSTEM Token
SID |
Description |
S-1-5-18 |
Unique to the SYSTEM context |
S-1-5-32-544 |
Local built-in domain administrators group |
S-1-1-0 |
World (everyone) |
S-1-5-11 |
Authenticated userall non-anonymous users |
The SYSTEM token contains local administrator rights to the computer and pretty much nothing else.
NOTE
The fact that the SYSTEM token has no network rights is important. Many administrators scratch their heads wondering why the neat little batch script works fine when they run it themselves but fails when run as a scheduled job by the scheduler service. The answer most often is that the script needs network rights (for example, it contains a NET command or NetBIOS path), and because the scheduler runs under SYSTEM context, the network access fails.
Note that because the SYSTEM token is a member of the local administrators group, processes running under this context can enable any privilege it might need.
Impersonation
Okay, back to tokens. As you saw, every process has a primary token that contains the security context of the user account associated with the process. By default, the system uses the primary token when a thread of the process interacts with a securable object.
However, a thread can impersonate a client account. This is a powerful feature that allows the thread to interact with securable objects using a client's security context. A thread impersonating a client has both a primary token and an impersonation token.
Impersonation is commonly used in server processes that handle requests from the network. For example, the Windows 2000 server service provides RPC support and file, print, and named pipe sharing. A quick look with PVIEW.EXE shows that the process is indeed running with the SYSTEM token. In fact, most server processes run with SYSTEM context. You'll note that the server service actually shares a binary with SERVICES.EXE, as several system services do. See Chapter 5, "Services," for more information about this technique.
Great, so the server service has local administrator power. But what happens when a client that has less than administrator privileges accesses a file share on this machine? Simple, the server service impersonates the client before attempting to access the resources. If the client does not have sufficient privilege, then neither will the server (while impersonating) and thus the access will fail. When the server service is done handling this client's requests, it can revert back to its primary (non-impersonated) token.
As another example, the Internet Information Server (IIS) process also runs under the SYSTEM context. However, administrators familiar with IIS will recognize the special IIS anonymous account (IIS_domainname) that IIS uses for anonymous clients (HTTP and FTP). The general idea is that you assign that special account the rights that you want for folks accessing your server anonymously.
What happens under the hood is that the IIS worker thread that actually services the request impersonates the guest account before accessing resources. When the user's request has been filled, the thread reverts back to its primary token.
Impersonation is a powerful and elegant way to handle access checks. You can imagine the alternative: Each server process, running as SYSTEM, would have to manually check access for each user to each object that he requests. This would require a huge volume of repetitive and error-prone code in each server service.
Restricted Tokens
New in Windows 2000 is the ability to create restricted tokens. Restricted tokens, as you might expect, contain a subset of the SIDs and privileges of the original token. You create restricted tokens using the CreateRestrictedToken() function, which simply takes as arguments a handle to an existing token as well as a list of privileges and SIDs to remove.
Token Security
With all this talk about opening tokens, I'm sure you're wondering how tokens are secured. Tokens are just kernel objects and thus, like every other kernel object, are securable and have security descriptors attached. See the SDK for the access rights that apply to tokens, as well as how to adjust them.
Moving On
Now that you're familiar with the background structures and concepts, look at the components that make up the Windows 2000 security architecture.