Main Contents »

Copyright © 2015 Ashok P. Nadkarni. All rights reserved.

1. Introduction

Distrust and caution are the parents of security.
— Benjamin Franklin

There are several different aspects to securing an application or system including

  • Authentication which involves an entity proving its identity

  • Authorization to determine what an entity is permitted to do in terms of actions and access to resources

  • Data protection to guard against tampering and to provide privacy as appropriate.

This chapter only deals with the base facilities in Windows related to the first two of these. The third aspect as well as some specialized topics like certificates and secure communications are dealt with elsewhere in the book. We also do not discuss Active Directory environments as there is a separate chapter devoted to the topic.

The sample code in this chapter requires the following packages:

  • twapi_security which includes commands for fundamental security related functionality such as access control

  • twapi_account which provides commands related to account management

  • twapi_process which provides commands related to processes

  • twapi_os which provides access to system information

% package require twapi_security
→ 4.1.27
% package require twapi_account
→ 4.1.27
% package require twapi_process
→ 4.1.27
% package require twapi_os
→ 4.1.27
% namespace path twapi

2. Concepts

We first introduce some basic concepts and terminology we will use in the rest of this chapter.

Security principal

A security principal is an entity whose identity can be authenticated. It may represent a real person, or a system.

Security credentials

A security principal authenticates using credentials that prove its identity. These credentials may take one of many forms, such as a password, a certificate, a smartcard and so on. A security principal may “own” several credentials, such as password and a certificate, through which it can be authenticated.

Trusted authorities

Consider the security principals Bob and Alice commonly used in security literature. Suppose Bob wants Alice to authenticate herself before she can access some resource owned by Bob. It is not possible in practice for every “Bob” in a network to know and keep track of credentials of every “Alice” to verify her identity. Therefore, in order for authentication to work, both Alice and Bob need to rely on a common authority that is trusted by Alice to safeguard private information required to authenticate her as well as by Bob to correctly verify Alice’s credentials.

An authority may be the local computer system, or a domain controller for a Windows domain, or even a certificate authority in the case of a public key system.

This trusted authority defines a scope within which the identifiers for security principals are issued. Thus Alice may have an identity defined with her local system authority and another, potentially with a different name, defined with a public certificate authority. Bob can authenticate Alice only if he trusts at least on of those two authorities.

In the case of Windows, the Local System Authority (LSA) is present on every system and defines a scope for security principal identities on that particular system.

Trustees

A trustee is a security principal, a group, or even a specific logon session that can be used as the subject in authorization rules. For example, the group Administrators is a trustee and rules can be set that accord every member of the group certain privileges, allow or deny access to certain resources, and so on.

A trustee is identified by both a human readable name, which might be duplicated on different systems, and a globally unique security identifier.

Accounts

An account is a record maintained by an authority that stores information about a security principal including among other things the principal’s name and credentials. An account has a scope that corresponds to that of the security authority in which it is defined.

Note Although the term account and security principal can often be used interchangeably in many contexts including in this chapter, they are not the same. An account is fundamentally a record that stores information about a security principal in the database of authority.
Security descriptor

A security descriptor is a structure directly or indirectly attached to a protected resource and defines the rules with respect to who can access the resource and for what purpose.

3. Security identifiers (SID’s)

For the most part, we are used to identifying trustees - users and groups - by their names, for example the group Administrators. In a Windows environment these names do not have to be unique either across systems or in time which makes them unsuitable for use in context such as access control. Instead, each trustee is associated with a security identifier (SID) which has this uniqueness property by including a a 96-bit value that uniquely identifies the system.

3.1. Mapping names and SIDs

We will look at what comprises an SID in a bit but first let us look at how one maps a name to its SID and vice versa using lookup_account_name and lookup_account_sid respectively.

% set sid [lookup_account_name Administrator]
→ S-1-5-21-604578671-2071180537-1579705201-500
% lookup_account_sid $sid
→ Administrator

Both will optionally retrieve some additional information about the account.

% lookup_account_name Administrator -all
→ -domain vulcan -type user -sid S-1-5-21-604578671-2071180537-1579705201-500
% lookup_account_sid $sid -all
→ -domain vulcan -type user -name Administrator

We can use the command to map groups as well.

% lookup_account_name Users -all
→ -domain BUILTIN -type alias -sid S-1-5-32-545

There are two things you may notice in this last example.

First, the type of the account is listed as alias. The type alias is nothing but an, uhm, alias for local group.

Second, the domain listed is called BUILTIN and is not the local system or a domain that the computer belongs to. This BUILTIN domain is present on all systems and contains accounts such as Users and Administrators that are created on every system when the operating system is installed.

We can use the -system option to map a name to an account on a different system.

% lookup_account_name Administrator -system vm3-win81pro -all
→ -domain VM3-WIN81PRO -type user -sid S-1-5-21-1620704929-12957813-3729018387-500
% lookup_account_name user1 -system vm3-win81pro -all
→ -domain VMAPN -type user -sid S-1-5-21-2723088876-595790134-303082780-1106

Notice that

  • the same name Administrator in fact has different SIDs on the two systems. The initial and last components of the SIDs are however the same, for reasons we will see in the next section.

  • the domains for the Administrator account and the user1 account are different even though both are looked up on the same system. The former is a local account defined on the vm3-win81pro system itself, while the latter is an account defined in the Active Directory domain to which the system belongs.

3.2. SID structure

Let us look now at the structure of an SID. Although a SID is stored in internally in a binary form, its structure is also representable as a string which has the format

S-REVISION-AUTHORITY-SUBAUTHORITY-SUBAUTHORITY…​

An SID string representation consists of a list of fields always starting with the letter S and separated by the - character.

The REVISION field determines the SID format and is always 1 as of this writing.

The AUTHORITY field partitions the space in which SIDs are defined. This is not to be confused with the term authority that we discussed earlier where the reference was to an entity that is trusted to identify and authenticate security principals. The meaning of the term will generally be obvious from context.

The values of this field are shown in SID Authorities.

Table 1. SID Authorities
Authority Value Description

NULL

0

Only one SID, S-1-0-0, is defined under this authority and it represents a group with no members.

WORLD

1

Again, only a single SID, S-1-1-0, is defined under this authority and it represents a group that includes all possible security principals.

LOCAL

2

This authority contains SID S-1-2-0 identifying a group named LOCAL which includes all security principals who logon to the system locally (physically at the console).

CREATOR

3

This authority includes SIDs that have specific meaning within access control lists. We will discuss these in detail later.

NT

5

Finally, this is the authority under which most built-in trustees as well as those defined by administrators fall.

The authority field is followed by one or more subauthority values, also known as relative identifier (RID). These form a hierarchy that collectively identify a trustee within the authority’s scope.

Here are a couple of examples:

% lookup_account_name Administrators
→ S-1-5-32-544
% lookup_account_name Administrator
→ S-1-5-21-604578671-2071180537-1579705201-500

Both these are defined under the NT authority as indicated by the S-1-5 prefix. The subauthorities differ in that in the first case the RID 32 indicates the built-in system domain while 544 identifies the local Administrators group no matter the system. In the second case, the last RID, 500, is a special RID indicating the local Administrator account and is the same across all systems. However, the parent RID components are specific to a particular system. Thus the SID for the Administrator account will be different on every system.

3.3. Well known SIDs

New SIDs are generated when user accounts and groups are created within an authority. In addition there are predefined well-known SIDs and RIDs that generically identify trustees. For example, the SID S-1-1-0 is the well-known SID representing the group Everyone which includes all trustees defined under any authority. Simlarly, the SID S-1-5-21-604578671-2071180537-1579705201-500 which is the well known RID 500 prefixed by the system’s SID always identifies the Administrator account on that system.

Well known SID’s are useful for creating generic access control rules. For example, you may want to allow access to certain resources only when the requesting user is logged on to the local console. This can be accomplished through an access control rule that only permits access to the SID S-1-2-1 which only includes trustees that are locally logged on to the console.

The full list of well-known SID’s is available in the MSDN documentation. Here we only describe some frequently used ones and how they might be used.

The table Well-known SID’s shows some very frequently used ones.

Table 2. Well-known SID’s

SID

Name

Description

S-1-1-0

Everyone

Built-in group that includes all users, including anonymous users and guests.

S-1-2-1

Console Logon

Built-in group that includes all users who are logged on to the physical console.

S-1-5-80-0

All Services

Built-in group that includes all processes running as services on the system (only in Vista and later).

S-1-5-1, S-1-5-2, S-1-5-3, S-1-5-4

Dialup, Network, Batch, Interactive

These SID’s correspond to built-in groups based on how a process was logged into a system. For example, you may wish restrict access to the camera only to the Interactive logon group.

S-1-5-18

Local System

The built-in SID for the operating system kernel.

S-1-5-19, S-1-5-20

Local Service, Network Service

Built-in accounts under which installed services are configured to run. The difference between the two is that the latter is permitted to access network resources using the credentials of the system.

S-1-5-32-544

Administrators

A built-in group to which accounts can be added to give them administrator privileges.

S-1-5-11

Authenticated users

A built-in group that includes all users that have been authenticated.

S-1-5-21-DOMAIN_RID-500

Administrator

The user account for the system administrator. Note this is different on every system/domain.

Tip When using built-in accounts, keep in mind that the name of the account is often localized. Thus the system administator account which is named Administrator on a US English system is named Administrateur on French versions of Windows. To retrieve the correct name for purposes such as display, or to retrieve the corresponding SID for constructing ACL’s, use the well_known_sid command to retrieve the SID and convert it to an account name with lookup_account_sid. We will see an example in the next section.

3.4. Working with SIDs

There are several commands available for working with SID’s. We have already seen a couple of these earlier, lookup_account_name and lookup_account_sid to convert back and forth between SIDs and account names. In addition, there are two commands map_account_to_name and map_account_to_sid which can take either a name or an SID as input. These are useful writing commands that may be passed either form.

% set name $::env(USERNAME)
→ ashok
% set sid [lookup_account_name $name]
→ S-1-5-21-604578671-2071180537-1579705201-1001
% lookup_account_sid $sid
→ ashok
% map_account_to_sid $sid
→ S-1-5-21-604578671-2071180537-1579705201-1001
% map_account_to_sid $name
→ S-1-5-21-604578671-2071180537-1579705201-1001
% map_account_to_name $sid
→ ashok
% map_account_to_name $name
→ ashok

There are separate commands, get_system_sid and get_primary_domain_info to retrieve the SID for the computer or the domain to which is belongs. For example,

% get_system_sid
→ S-1-5-21-604578671-2071180537-1579705201

Finally, well-known SID’s are constructed with the well_known_sid command.

% set admin_sid [well_known_sid accountadministrator -domainsid [get_system_sid]]
→ S-1-5-21-604578671-2071180537-1579705201-500
% set admin_name [lookup_account_sid $admin_sid]
→ Administrator

See the TWAPI documentation for the list of well-known SID identifiers that can be passed to the well_known_sid command.

In many cases, you can also pass the name of the account as it would be called on a US English system. This can be easier to remember.

% set admin_sid [well_known_sid Administrator -domainsid [get_system_sid]]
→ S-1-5-21-604578671-2071180537-1579705201-500

4. Locally Unique Identifiers

Internally, Windows stores privileges as 64-bit binary values A Locally Unique Identifiers (LUID) is a 64-bit binary value generated through the AllocateLocallyUniqueId Win32 system call. The call guarantees that each returned value will be unique only for that system and even then only until it is restarted. Hence the Locally part of the name.

From Tcl, the new_luid command generates LUID’s.

% new_luid
→ 00000000-279a2dfb
% new_luid
→ 00000000-279a2dfc

LUID’s are used internally by the system to identify many objects including privileges, logon sessions and security tokens. We will run into them throughout this chapter.

5. Users and groups

Having looked at the basic concepts and terminology, we are now ready to explore them programmatically. We start by looking at trustees. A trustee

  • may be a single security principal or a group.

  • is globally identified uniquely by a security identifier (SID)

  • is locally identified within the scope of the defining authority by a human-readable name

  • maybe built-in or predefined by Windows, such as the Administrators group, or defined by the system administator

The most frequently encountered types of trustees are users and groups though we will see other types later. A user is the colloquial term used for a security principal. The system (actually the trusted authority for the principal) stores information about each user in a user account record. This includes information related to security, such as that related to credentials, system related information, such as home directory, and potentially even personal details that are not relevant for our purposes.

5.1. Local and domain users

A user account represents a security principal, and as we never tire of repeating, a security principal is always defined within the scope of a trusted authority. In a Windows environment, this trusted authority may be the local system, in which case the account is stored in the local system’s SAM database, or the domain controller in which case the account is stored in the Active Directory repository for the domain.

5.2. Account names

Local account names have the general form

SYSTEMNAME\USERNAME

where SYSTEMNAME is the name of the system in whose SAM database USERNAME is defined. In many cases, just USERNAME suffices as SYSTEMNAME defaults to the local system if unspecified.

Domain names are specified using a format known as the User Principal Name (UPN) which has the form

USERNAME@DNSSTYLESUFFIX

This form is similar to that used for email though note that the similarity is purely syntactic. The name does not have to correspond to an email address at all.

Tip The DNSSTYLESUFFIX suffix is commonly the DNS name of the domain but this is not mandated. The domain administrator can explicitly assign any value and this does not even have to be the same for all users in the domain. The only restriction is that the entire UPN has to be unique in the domain. For example, the domain xyz.com may have accounts with UPN bob@xyz.com and alice@abc.net.

5.3. Groups and aliases

Groups provide a mechanism for identifying collections of security principals and are discussed in detail in the [chap_adsi] chapter. Here we provide a much simplified description that suffices for the purposes of this chapter.

A global group is a collection of security principals each of whom is contained within the domain defining the group. Global groups can be nested so that a principal may indirectly be a member of a global group. A global group can be used within the domain definining it or within any trusting domain.

A local group, or an alias, can be used only within its domain but can contain user accounts and global groups from multiple domains. On a system that is not a domain controller, only local groups defined on that system can be used, not those defined on other systems. Note that local groups cannot be nested.

The primary purpose of groups is to assign privileges and resource access permissions to multiple security principals without having to manage them individually.

5.4. Managing users and groups

TWAPI provides a comprehensive set of commands for managing both user accounts and groups. Here we only illustrate basic usage through some examples. For details and a full list of command options, refer to the TWAPI documentation.

The commands and their purpose are shown in Commands for user and group management.

Table 3. Commands for user and group management
Purpose Users Global groups Local groups

Enumeration

get_users

get_local_groups

get_global_groups

Creation

new_user

new_local_group

new_global_group

Deletion

delete_user

delete_local_group

delete_global_group

Retrieving settings

get_user_account_info

get_local_group_info

get_global_group_info

Modifying settings

set_user_account_info

set_local_group_info

set_global_group_info

Enumerating members

get_local_group_members

get_global_group_members

Checking membership

get_user_local_groups

get_user_global_groups

Adding members

add_member_to_local_group

add_user_to_global_group

Removing members

remove_member_from_local_group

remove_user_from_global_group

We now illustrate basic operations for managing user accounts including

  • enumerating accounts

  • account creation

  • retrieving account settings

  • modifying account settings

  • account deletion

  • group membership

The commands pertaining to groups are very similar to those for user accounts including options and return formats. We therefore do not explicitly illustrate them here.

Security principals, and consequently user accounts as well, are defined in the context of a trusted authority as we discussed earlier. This trusted authority is by default the local system and consequently the commands described here operate on the system where the command is invoked. For example, the get_users command will by default retrieve the list of local user accounts.

% get_users
→ Administrator Guest vm3admin

However, for most commands, the -system option can be used to target remote systems (and equivalently, a different trusted authority) as well. In particular, when the target system specified is the domain controller, the commands work at the domain level.

% get_users -system [find_domain_controller]
→ Administrator Guest krbtgt vm2admin user1 {VM2-W2K12R2$} {VM3-WIN81PRO$}
Note These commands and those discussed later all assume the calling process has an authenticated connection with the target system, whether local or remote, with the required privileges.

We will use domain level accounts for our illustration so we start off by retrieving the name of the domain and the domain controller.

% set domain_name [dict get [get_primary_domain_info -name] -name]
→ VMAPN
% set domain_controller [find_domain_controller]
→ \\VM2-W2K12R2.vm.apn
Enumerating accounts

Let us see what accounts are already present in the domain.

% get_users -system $domain_controller
→ Administrator Guest krbtgt vm2admin user1 {VM2-W2K12R2$} {VM3-WIN81PRO$}

Notice the returned accounts include accounts for computers belonging to the domain (just two in our test environment).

When enumerating accounts as above, we could have also retrieved more detailed information about each by specifying the -level option.

% recordarray iterate user [get_users -level 1 -system $domain_controller] {
    puts "User: $user(-name)"
    parray user
}
→ User: Administrator
  user(-comment)      = Built-in account for administering the computer/domain
  user(-flags)        = 513
  user(-home_dir)     =
  user(-name)         = Administrator
  ...Additional lines omitted...
Tip When a system, or more likely a domain, has a very large number of users, enumerating users as above can be expensive in terms of processing time and memory usage. In this case, it is advisable to use the -resume options to the get_users command which results in the data being returned in incremental fashion. See the TWAPI documentation for details.
Creating accounts

Let us now look at creating new user accounts.

We can use the read_credentials command, described later in this chapter, to prompt the user for the name of the new account and the password to be assigned.

% lassign [read_credentials -target "Domain $domain_name"] name password
→ Enter the user name for 'Domain VMAPN': alice
  Enter the password for Domain VMAPN:
  Remember this password? (Y/N)n
  0

A new user account is then created using the submitted name and password. We verify that the new account is indeed included when we enumerate domain accounts.

% new_user $name -system $domain_controller -password $password
% get_users -system $domain_controller
→ Administrator Guest krbtgt vm2admin user1 alice {VM2-W2K12R2$} {VM3-WIN81PRO$}
Setting and reading account information

Additional configuration for the account can be done through the various options to the set_user_account_info command.

% set_user_account_info alice -comment "Bob's partner in crime" -acct_expires \
    never -system $domain_controller

We can use the get_user_account_info command to retrieve all fields stored in the account record (except credentials for obvious reasons) and verify the settings are as desired.

% print_dict [get_user_account_info alice -all -system $domain_controller]
→ -comment {Bob's partner in crime}
Deleting accounts

Our illustration is done, so we can get rid of the account as we really don’t want it hanging around.

% delete_user alice -system $domain_controller
% get_users -system $domain_controller
→ Administrator Guest krbtgt vm2admin user1 {VM2-W2K12R2$} {VM3-WIN81PRO$}
Checking group membership

The commands for enumerating group members, get_local_group_members and get_global_group_members are very similar to to the previously discussed enumeration commands for users and groups.

% recordarray iterate acct [get_local_group_members Administrators -level 1] {
    puts ----
    puts "$acct(-name)"
    parray acct
}
→ ----
  Administrator
  acct(-name)     = Administrator
  acct(-sid)      = S-1-5-21-1620704929-12957813-3729018387-500
  acct(-sidusage) = 1
  ----
  vm3admin
  acct(-name)     = vm3admin
  acct(-sid)      = S-1-5-21-1620704929-12957813-3729018387-1001
  acct(-sidusage) = 1
  ...Additional lines omitted...

Notice how a global group Domain Admins can be a member of the local group Administrators.

Conversely, we can list the groups that contain a specified account. For example, to list the global groups that an account belongs to

% get_user_global_groups vm2admin -system $domain_controller
→ {Domain Admins} {Domain Users}

Note that the account vm2admin is an account on the domain controller (and hence contained in the VMAPN domain), and not an account locally defined. Let us try to query for the local system for what local groups it belongs to.

% get_user_local_groups vm2admin 1
→ The user name could not be found.
% get_user_local_groups VMAPN\\vm2admin 2
% get_user_local_groups VMAPN\\vm2admin -recurse 1 3
→ Administrators Users
1 Unqualified name
2 Qualified name
3 Include indirect membership

In the first case, we get an error because we have not qualified it with the name of the domain. In the second case, we do not get an error because we have qualified the account name with the name of the domain VMAPN but the returned list is empty. This is because VMAPN\vm2admin is not a direct member of any local group. In the third case, we ask that indirect membership also be checked. Because vm2admin is a member of the global groups Domain Admins and Domain Users which in turn are members of local groups Administrators and Users respectively, these local groups are returned by the third command.

Just to verify the above,

% get_user_global_groups vm2admin -system $domain_controller
→ {Domain Admins} {Domain Users}
% get_local_group_members Administrators
→ Administrator vm3admin {Domain Admins}
% get_local_group_members Users
→ INTERACTIVE {Authenticated Users} vm3admin {Domain Users}
Changing group membership

Adding to a local or global group is easily accomplished with add_member_to_local_group and add_user_to_global_group.

% add_member_to_local_group Users VMAPN\\vm2admin
% get_local_group_members Users
→ INTERACTIVE {Authenticated Users} vm3admin {Domain Users} vm2admin

Conversely, membership can be cancelled with remove_member_from_local_group and remove_user_from_global_group respectively.

% remove_member_from_local_group Users VMAPN\\vm2admin
% get_local_group_members Users
→ INTERACTIVE {Authenticated Users} vm3admin {Domain Users}

6. Privileges and rights

Privileges are assigned to security principals in the context of a particular system and confers upon them the ability to take certain actions such as shutting down the system. A privilege is assigned to a trustee. In the case where a trustee is a group, the security principals belonging to the group are assigned the privilege.

Account rights are similar except that they specifically refer to the type of logon (interactive, network, etc.) a security principal can perform.

As privileges and rights are for the most part treated identically from a programming perspective, in this chapter we refer to them collectively as ''privileges'' and only distinguish them when they differ.

For a full list of privileges and rights defined in Windows, refer to the MSDN documentation.

6.1. Managing trustee privileges

Enumerating trustee privileges

The get_account_rights command will list the privileges and rights assigned to a trustee.

% get_account_rights Administrator
% get_account_rights Administrators
→ SeSecurityPrivilege SeBackupPrivilege SeRestorePrivilege SeSystemtimePrivileg...

Notice that the Administrator account does not have any privileges or rights assigned. It is however a member of the Administrators group from which it inherits the privileges that allow system administrative tasks to be carried out.

Finding trustees with a privilege

Conversely, we can find out which trustees hold a specific privilege. So to find out which trustees are permitted to impersonate other users,

% find_accounts_with_right SeImpersonatePrivilege
→ S-1-5-6 S-1-5-32-544 S-1-5-20 S-1-5-19
% find_accounts_with_right SeImpersonatePrivilege -name
→ SERVICE Administrators {NETWORK SERVICE} {LOCAL SERVICE}
Adding and removing privileges

Assigning and removing privileges from trustees is straightforward with the add_account_rights and remove_account_rights command. We illustrate this on our test domain controller.

% get_account_rights vm2admin -system $domain_controller
% add_account_rights vm2admin {SeDebugPrivilege SeShutdownPrivilege} -system \
    $domain_controller
% get_account_rights vm2admin -system $domain_controller
→ SeDebugPrivilege SeShutdownPrivilege
% remove_account_rights vm2admin {SeDebugPrivilege SeShutdownPrivilege} -system \
    $domain_controller
% get_account_rights vm2admin -system $domain_controller

You can map privilege names to the internally stored LUID’s and back using the commands map_privilege_to_luid and map_luid_to_privilege

% set luid [map_privilege_to_luid SeDebugPrivilege]
→ 00000000-00000014
% map_luid_to_privilege $luid
→ SeDebugPrivilege

When programming in Tcl, you rarely need to use LUID’s directly. They are described here in case you run across them in other documentation or programs that deal with privileges.

That is all we will discuss about privileges for now. We will have more to say about their use after we introduce security contexts and process tokens later in this chapter.

7. Access tokens and user contexts

When a security principal authenticates to a Windows system, the system LSA creates a new logon session along with a structure, the access token, that encapsulates all the security related information for that principal including amongst other things its SID, the SID’s for the groups it belongs to and assigned privileges.

When a new process is created to run within that logon session, a copy of this token is attached to it. A copy is made because as we shall see, some fields within the token can be modified so that different processes within a logon session need to have separate tokens.

This token assigned to a process, and by default to the threads it contains, is called its primary token. Threads can also can run with an impersonation token which present a different security context than that of the primary token. We talk about impersonation in detail in the Impersonation section.

Irrespective of whether a primary or impersonation token is in effect, it figures in a number of security related operations:

  • When a secured resource is accessed, the security principal SID and associated group SID’s stored in the token are used for checking whether access is permitted. We discuss this in detail in Access Control.

  • When a new secured object is created, defaults are applied to the access control rules to be associated with the object if the creating application itself does not specify any rules. These defaults are stored in the token. Again, this is discussed in Access Control.

  • When a thread invokes a protected system action such as shutting down the system, the system checks whether the appropriate privilege, SeShutdownPrivilege in this case, is present and enabled in the token.

  • The mandatory integrity controls introduced in Vista are also based on integrity levels stored in the token. This is described in Mandatory Integrity Control.

Because access tokens are used in many different contexts for many different purposes, here we only describe the basic contents. Other aspects of tokens will be detailed when we talk about the different scenarios where they are used.

7.1. Obtaining a token handle

We start off by examining some basic information stored in a token. A token is a kernel object and cannot be accessed directly. Rather, to do anything with a token, we first need to obtain a handle to it with the open_process_token call.

% set tok [open_process_token -access token_all_access]
→ 400 HANDLE

This gives us a handle to the primary token for the process running our Tcl shell. We could have also opened a token for some other process or thread to which we have access rights by providing its process or thread ID.

Note We have a bit of a chicken-and-egg problem here as we have not discussed access control as yet but cannot do that without first describing basics of tokens. For now, it is sufficient to understand that the above call will give us a handle that will let us invoke all possible actions on the token.

You can also create a new token and retrieve a handle to it. This is accomplished through the open_user_token call which we will work with in the [chap_processes] chapter.

7.2. Token users and groups

The capabilities of a token are based primarily on the security principal and groups it represents so we begin by examining these.

The get_token_info and get_token_stats commands can be used to examine the token contents. In many cases, there are equivalent commands that will provide values of individual fields.

% print_dict [get_token_info $tok -usersid]
→ -usersid   = S-1-5-21-604578671-2071180537-1579705201-1001
% get_token_user $tok
→ S-1-5-21-604578671-2071180537-1579705201-1001
% get_token_user $tok -name
→ ashok

Listing the groups in the token is slightly more complicated because associated with each group are attributes that describe characteristics of the group and how it can be used.

% foreach {sid attrs} [get_token_groups_and_attrs $tok] {
    puts "[lookup_account_sid $sid] -> [join $attrs {, }]"
}
→ None -> mandatory enabled_by_default enabled
  Everyone -> mandatory enabled_by_default enabled
  Local account and member of Administrators group -> use_for_deny_only
  Netmon Users -> mandatory enabled_by_default enabled
  Administrators -> use_for_deny_only
  Users -> mandatory enabled_by_default enabled
  INTERACTIVE -> mandatory enabled_by_default enabled
  CONSOLE LOGON -> mandatory enabled_by_default enabled
  Authenticated Users -> mandatory enabled_by_default enabled
  This Organization -> mandatory enabled_by_default enabled
  Local account -> mandatory enabled_by_default enabled
  Logon SID -> mandatory enabled_by_default enabled logon_id
  LOCAL -> mandatory enabled_by_default enabled
  NTLM Authentication -> mandatory enabled_by_default enabled
  Medium Mandatory Level -> integrity integrity_enabled

The groups included in the token come from multiple sources.

  • Some, like Administrators, are statically bound to the security principal as part of the user account database.

  • Some, like Users are built-in

  • Some, like INTERACTIVE or CONSOLE LOGON, are added based on type of logon and authentication

  • The special Logon SID is a group containing all processes in the current logon session and discussed in further detail in Logon Sessions.

The only group attribute we discuss here is use_for_deny_only

attribute. This marks the group as a deny-only group which means it is only considered for access control rules that disallow access to resources. This is explained further in Discretionary Access Control. We shall also discuss why the Administrators group is specifically marked deny-only in User Account Control.

7.3. Token identification

Every token has a LUID that uniquely identifies it in the system. This is shown as the field ludi in the output below. Any time a token is created, including through duplication of an existing one, the newly created token gets a new LUID. In addition, a separate field modificationluid exists that allows us to track when a token has been modified. This field is changed by the system whenever any call is made that modifies the token.

% print_dict [get_token_statistics $tok] luid modificationluid
→ luid               = 00000000-2799a569
  modificationluid   = 00000000-2797d6c4
% enable_token_privileges $tok SeShutdownPrivilege
→ SeShutdownPrivilege
% print_dict [get_token_statistics $tok] luid modificationluid
→ luid               = 00000000-2799a569
  modificationluid   = 00000000-279a2e2c
% disable_token_privileges $tok SeShutdownPrivilege
→ SeShutdownPrivilege
% print_dict [get_token_statistics $tok] luid modificationluid
→ luid               = 00000000-2799a569
  modificationluid   = 00000000-279a2e33

Note how luid is unchanged but the modificationluid is different after every modification to the token. (We will look at enable_token_privileges in just a bit.)

Tip When might one make use of the modificationluid value? A possible use is as an optimization when implementing access checks for your own private or custom objects (for system objects you generally do not need to implement this yourself). After making an access check, you might cache the result of the check is based on the object’s DACL and the token modificationluid field. Next time the same check needs to be made, if the DACL is unchanged and the token’s modificationluid field is unchanged, you can use the cached result without reissuing an access check request. Personally speaking, the value of this is debatable.

7.4. Token type

The type of the token, primary or impersonation, is returned by the get_token_type command.

% get_token_type $tok
→ primary

7.5. Token logon session

The logon session owning the token can be retrieved either with get_token_info or get_token_statistics. In the latter case, the authluid key holds this information.

% get_token_info $tok -logonsession
→ -logonsession 00000000-002cd541
% dict get [get_token_statistics $tok] authluid
→ 00000000-002cd541

This can be used to find if two processes belong to the same logon session or not.

7.6. Token privileges

The privileges assigned to a user account along with those assigned to any groups to which it belongs are stored as part of the token. This is one area where privileges are account rights differ. The token only stores the former, not the latter. This makes sense because account rights only deal with logons and by the time a token is created the logon has already taken place.

Important The privileges are collected into the token at the time the security principal is authenticated and the logon session created. If privileges are added or removed from the relevant account or groups after this takes place, the privileges listed in the token are unaffected. Only tokens in logon sessions created afterwards will see the new privilege settings.

To prevent inadvertent use of privileges, they are stored in the token as enabled or disabled. If a Windows API call requires a privilege, that privilege must be present in the token of the caller and must be enabled.

The list of enabled privileges can be retrieved with get_token_privileges.

% get_token_privileges $tok
→ SeChangeNotifyPrivilege SeImpersonatePrivilege SeCreateGlobalPrivilege

Specifying the -all option will return two lists - the first containing enabled privileges and the second containing privileges that are present in the token but disabled.

% get_token_privileges $tok -all
→ {SeChangeNotifyPrivilege SeImpersonatePrivilege SeCreateGlobalPrivilege} {SeI...
Enabling privileges

To make a Windows call that requires one or more privileges, the privileges must be enabled before the call is made. Moreover, recommended security practices require that the privileges be disabled afterward when no longer needed.

Note You can enable privileges in a token that are present but disabled. For obvious reasons, you cannot add a new privilege that is not present!

The commands enable_token_privileges and disable_token_privileges can be used for this purpose. However, for the usual case where a process is enabling its own privileges, it is more convenient to use the functions enable_privileges and disable_privileges as these take care of obtaining and releasing the required token.

Whichever you use, there are two finer points to be aware of when enabling and disabling privileges with the goal that the enabled privileges in the token after the enable/disable sequence must be exactly the same as before.

  • The first is that you must ensure that privileges are restored properly even in case of errors.

  • The second more subtle point is that you cannot just pass the privileges you want to enable_privileges and then pass them to disable_privileges. The problem in doing this is that one of the privileges passed to enable_privileges might already be enabled in the token. Then when the privilege is passed into disable_privileges, it lands up being disabled which the result that the enabled privileges after the sequence are not the same as before.

Both enable_privileges and disable_privileges help dealing with this latter point by returning a list of privileges that actually changed state as a result of the call. These can then be passed to the complementary call to return the token to its exact previous state (in terms of privileges).

The general recommended pattern is then similar to the following.

% set newly_enabled_privs [enable_privileges {SeChangeNotifyPrivilege \
    SeShutdownPrivilege}]
→ SeShutdownPrivilege
% try {
 1
} finally {
    disable_privileges $newly_enabled_privs
}
1 Make your privileged calls

This pattern is common enough that there is a command — eval_with_privileges — specifically geared towards it. The above sequence would be replaced by

% eval_with_privileges {
 1
} {SeChangeNotifyPrivilege SeShutdownPrivilege}
1 Make your privileged calls

which is a more convenient interface.

Important The commands enable_privileges, disable_privileges and eval_with_privileges manipulate the process primary token only and therefore are not suitable for use in threads that are impersonating. In those cases, you must use the enable_token_privileges and disable_token_privileges calls to explicitly manage the token.

7.7. Closing a token handle

Once you are done using a token, you must close its handle with close_token.

% close_token $tok

We are now ready to tackle the core of this chapter — controlling access to resources.

8. Access Control

Now that we have discussed the basics of users and groups, we can move on to the core of this chapter - securing access to resources where the resource may range from files and the registry to kernel objects such as processes and semaphores.

There are three parts to access control in Windows:

  • Mandatory Integrity Control (MIC) which assigns integrity levels to objects as an indication of ''trustworthiness'' and permits access based these. MIC is for the most part mandatorily enforced by the system without user control (hence the name).

  • Discretionary Access Control (DAC) fine-grained rules that control different types of access to resources by different security principals. These rules can be defined by users and applications at their discretion (and again, hence the name).

  • Security audit generation where attempted accesses to securable objects, whether permitted or denied, are written to an audit log. This is strictly a monitoring facility and does not interfere with the actual access itself.

All three are based on the same set of mechanisms. When a process, or to be more precise a thread, needs to access a resource, it has to first acquire a handle to the resource and makes a request to the system for this purpose. The system grants or denies the request for a handle based on the following factors:

  • The security context for the thread, represented by an access token which encapsulates the trustee information (user identity, group memberships etc.) on whose behalf the thread is executing.

  • The requested access type, which may be generic, such as reading resource content, or specific to the resource type, for example stopping a process.

  • Rules contained within a security descriptor associated with the resource. This descriptor contains the rules associated with MIC, DAC, and audit generation.

  • The MIC rules are examined first and if they do not permit access, access is denied. If the MIC rules permit access, the DAC rules are examined next to determine whether access is allowed. Independent of the outcome, an event is logged based on the audit generation rules.

We have already examined access tokens in a previous section. We now expound on the remaining mechanisms. We will first describe these in the context of discretionary access control and then expand on them to cover integrity control and security auditing.

8.1. Access rights

Access rights are associated with specific operations, or sets of operations, on a resource. When a thread requests a handle to a resource, it passes in a list of access rights that it desires based on the operations it wants to invoke. Conversely, the security descriptor associated with the resource stores the access rights that it is prepared to grant to different trustees. The operating system matches the two along with the requesting thread’s security context which holds the trustee on whose behalf it is executing and decides whether a handle with the desired access should be granted.

Warning Do not confuse the use of the term ''rights'' in this context with ''rights'' as used in discussing Privileges and rights. In that context, ''rights'' were associated with trustee accounts and controlled the type of logons they were permitted to perform. Here ''rights'' are associated with operations on resources.

The system defines certain access rights that apply to all resource types. These are called standard rights. In addition, each resource type has its own set of access rights defined that only make sense for that type. These are called specific rights.

Standard rights

The standard rights applicable to all resource types are shown in Standard rights.

Table 4. Standard rights
Name Operation for which permission is requested/granted

delete

Deletion of the resource

synchronize

Use of the handle to the resource as a synchronization object

read_control

Reading the resource’s security descriptor except the SACL component

write_dac

Modifications of the resource security descriptor DACL component

write_owner

Change ownership of the resource as stored in the security descriptor

standard_rights_all

Combination of all the above

standard_rights_execute

Same as read_control

standard_rights_read

Same as read_control

standard_rights_required

Combination of delete, read_control, write_dac, write_owner

standard_rights_write

Same as read_control

We will expand on some of the above descriptions as we go along but for now do not be mislead by the terminology. For example, the standard_rights_read right does not pertain to reading a file content; it refers to reading the file’s security descriptor.

Specific rights

Specific rights, which depend on the resource type, are shown in Specific access rights. The semantics are not described but for the most part should be obvious from the name. Otherwise, refer to the Windows SDK.

Table 5. Specific access rights
Resource type Rights

COM objects

com_rights_execute, com_rights_execute_local, com_rights_execute_remote, com_rights_activate_local, com_rights_activate_remote

Desktops

desktop_readobjects, desktop_createwindow, desktop_createmenu, desktop_hookcontrol, desktop_journalrecord, desktop_journalplayback, desktop_enumerate, desktop_writeobjects, desktop_switchdesktop

Events

event_all_access, event_modify_state

Files

file_all_access, file_generic_read, file_generic_write, file_generic_execute, file_read_data, file_write_data, file_append_data, file_read_ea, file_write_ea, file_execute, file_delete_child, file_read_attributes, file_write_attributes

Mutexes

mutex_all_access, mutex_modify_state

Named pipes

file_all_access, file_read_data, file_write_data, file_create_pipe_instance, file_read_attributes, file_write_attributes

Processes

process_all_access, process_terminate, process_create_thread, process_set_sessionid, process_vm_operation, process_vm_read, process_vm_write, process_dup_handle, process_create_process, process_set_quota, process_set_information, process_query_information, process_suspend_resume

Registry

key_all_access, key_read, key_write, key_execute, key_query_value, key_set_value, key_create_sub_key, key_enumerate_sub_keys, key_notify, key_create_link, key_wow64_32key, key_wow64_64key, key_wow64_res

Semaphores

semaphore_all_access, semaphore_modify_state

Services

service_all_access, service_query_config, service_change_config, service_query_status, service_enumerate_dependents, service_start, service_stop, service_pause_continue, service_interrogate, service_user_defined_control

Threads

thread_all_access, thread_terminate, thread_suspend_resume, thread_get_context, thread_set_context, thread_set_information, thread_query_information, thread_set_thread_token, thread_impersonate, thread_direct_impersonation

Timers

timer_all_access, timer_query_state, timer_modify_state

Tokens

token_all_access, token_assign_primary, token_duplicate, token_impersonate, token_query, token_query_source, token_adjust_privileges, token_adjust_groups, token_adjust_default, token_adjust_sessionid

Window stations

winsta_enumdesktops, winsta_readattributes, winsta_accessclipboard, winsta_createdesktop, winsta_writeattributes, winsta_accessglobalatoms, winsta_exitwindows, winsta_enumerate, winsta_readscreen, winsta_all

8.2. Access Control Entries

Now that we know all about trustees and access rights, we are ready to discuss the basic unit of access control - the Access Control Entry (ACE).

An ACE maps a single trustee (which may be a group) and a set of access rights to an allow or deny action. There are many other action types as well but we will just deal with these, which pertain to discretionary access control, for now. Two others, audit and mandatory_label, we discuss later in this chapter while the others pertain only to Active Directory objects and are therefore described in [chap_adsi].

8.2.1. Working with ACE’s

As an example, let us create an ACE with the new_ace command that allows the Users built-in group read-only access to a file. Note the ACE is not actually ''attached'' to a file as yet; it is simply a rule at this point.

% set ace [new_ace allow [well_known_sid Users] {file_read_data file_execute}]
→ 0 0 33 S-1-5-32-545

The returned value should be treated as opaque but we can extract fields from it as well as modify it with various commands.

We can change the trustee for the ACE …​

% get_ace_sid $ace
→ S-1-5-32-545
% set ace [set_ace_sid $ace [well_known_sid Guests]]
→ 0 0 33 S-1-5-32-546
% get_ace_sid $ace
→ S-1-5-32-546

…​or the applicable rights…​

% get_ace_rights $ace -resourcetype file
→ file_read_data file_execute
% set ace [set_ace_rights $ace {file_read_data file_execute file_write_data}]
→ 0 0 35 S-1-5-32-546
% get_ace_rights $ace -resourcetype file
→ file_read_data file_write_data file_execute

…​or even change the ACE to a deny

% get_ace_type $ace
→ allow
% set ace [set_ace_type $ace deny]
→ 1 0 35 S-1-5-32-546

We can even get the human-readable form of the ACE with the get_ace_text command.

% get_ace_text $ace -resourcetype file
→ Type: Deny
  User: S-1-5-32-546 (Guests)
  Rights: file_read_data, file_write_data, file_execute
  Inherited: No
  Include self: Yes
  Recurse containers: No
  Recurse objects: No
  Recurse single level only: No

The first three fields displayed are what we have discussed so far. The remaining fields have to do with inheritance of ACE’s which we will talk about next.

8.2.2. ACE inheritance

Many resource types have the notion of containers, the most obvious example being directories in a file system. Access control entries set on a parent container can be propagated to its descendents.

This inheritance of ACE’s can be controlled when creating an ACE through various options passed to the new_ace command shown in Ace inheritace options.

Table 6. Ace inheritace options
Option Description

-recursecontainers BOOLEAN

If the specified value is 1, the ACE is also applied to all descendents of the object to which the ACE is attached that are themselves containers. For example, in the case of a file ACE, this would indicate that the ACE also apply to all subdirectories. If 0 (default), the ACE does not apply to descendents that are containers.

-recurseobjects BOOLEAN

If the specified value is 1, the ACE is also applied to all descendents of the object to which the ACE is attached that are not themselves containers. For example, in the case of a file ACE, this would indicate that the ACE also apply to all files under all subdirectories below a directory but not to the subdirectories themselves. If 0 (default), the ACE does not apply to non-container objects.

-recurseonelevelonly BOOLEAN

If the specified value is 0 (default), the -recursecontainers and -recurseobjects options have effect at all levels below the container object to which the ACE is applied. If the value is 1, the ACE is only applied to the immediate children of the container object. This option has no effect if neither -recurseobjects nor -recursecontainers is specified.

-self BOOLEAN

If the specified value is 1 (default), the ACE applies to the object to which it is attached. If the specified value is 0, then the created ACE does not apply to the object with which it is attached. In this case, it only applies to the descendents of that object as indicated by the other options.

Inheritance of an existing ACE can also be retrieved and modified with get_ace_inheritance and set_ace_inheritance.

% set ace [new_ace allow [well_known_sid Users] {file_read_data file_execute} \
    -recursecontainers 1 -recurseobjects 1]
→ 0 3 33 S-1-5-32-545
% get_ace_inheritance $ace
→ -self 1 -recursecontainers 1 -recurseobjects 1 -recurseonelevelonly 0 -inherited
   ∟ 0
% set ace [set_ace_inheritance $ace -self 0 -recurseonelevelonly 1]
→ 0 15 33 S-1-5-32-545
% get_ace_inheritance $ace
→ -self 0 -recursecontainers 1 -recurseobjects 1 -recurseonelevelonly 1 -inherited
   ∟ 0

There are two additional points to note about ACE inheritance:

  • Propagated entries need to be merged with any ACE’s set directly on the descendant resource. We describe this in Ordering of ACE’s.

  • The security descriptor for a resource can be configured not to include inherited ACE’s. This is described in Security descriptors.

8.3. Access Control Lists

An Access Control List is an ordered list of ACE’s each of which, as we saw above, maps a single trustee and a set of rights to an allow or deny action.

Protecting a resource generally involves setting rules for multiple combinations of trustees and rights. A single ACE is therefore insufficient for the purpose and therefore what is attached to a resource is an access control list (ACL) which is an ordered list of ACE’s each of which deals with a combination of trustee and access rights of interest.

As we shall see, ACL’s are involved in all three aspects of access control:

  • Discretionary Access Control Lists (DACL’s), as the name implies, contain the rules pertaining to discretionary access control.

  • System Access Control Lists (SACL’s) contain settings for mandatory integrity control and security auditing.

Both these ACL’s are attached to the security descriptor for a resource. As an example, here is the DACL attached to to our drive’s root directory.

% get_acl_text [get_security_descriptor_dacl [get_resource_security_descriptor \
    file c:/]] -resourcetype file
→ Rev: 2
  ACE #1
    Type: Allow
    User: S-1-5-32-544 (Administrators)
    Rights: All
    Inherited: No
    Include self: Yes
    Recurse containers: Yes
    Recurse objects: Yes
    Recurse single level only: No
  ACE #2
    Type: Allow
    User: S-1-5-18 (SYSTEM)
    Rights: All
    Inherited: No
    Include self: Yes
    Recurse containers: Yes
    Recurse objects: Yes
    Recurse single level only: No
  ACE #3
    Type: Allow
    User: S-1-5-32-545 (Users)
    Rights: standard_rights_read, standard_rights_write, standard_rights_execut...
    Inherited: No
    Include self: Yes
    Recurse containers: Yes
    Recurse objects: Yes
    Recurse single level only: No
  ACE #4
    Type: Allow
    User: S-1-5-11 (Authenticated Users)
    Rights: delete, generic_read, generic_write, generic_execute
    Inherited: No
    Include self: No
    Recurse containers: Yes
    Recurse objects: Yes
    Recurse single level only: No
  ACE #5
    Type: Allow
    User: S-1-5-11 (Authenticated Users)
    Rights: file_append_data
    Inherited: No
    Include self: Yes
    Recurse containers: No
    Recurse objects: No
    Recurse single level only: No

8.3.1. Working with ACL’s

An ACL is primarily a list of ACE’s so constructing one is straightforward using new_acl and passing it a list of ACE’s.

% set dacl [new_acl [list [new_ace allow Users file_read_data] [new_ace allow \
    Administrators [list file_all_access]]]]
→ 2 {{0 0 1 S-1-5-32-545} {0 0 2032127 S-1-5-32-544}}

Like for ACE’s, the returned value should be treated as opaque and manipulated through commands. Additional ACE’s can be appended or prepended with append_acl_aces and prepend_acl_aces.

% set dacl [append_acl_aces $dacl [list [new_ace allow Guests file_read_data]]]
→ 2 {{0 0 1 S-1-5-32-545} {0 0 2032127 S-1-5-32-544} {0 0 1 S-1-5-32-546}}
% set dacl [prepend_acl_aces $dacl [list [new_ace deny Administrators write_dac]]]
→ 2 {{1 0 262144 S-1-5-32-544} {0 0 1 S-1-5-32-545} {0 0 2032127 S-1-5-32-544} ...

You can retrieve the ACE’s present in the ACL with get_acl_aces

% foreach ace [get_acl_aces $dacl] { puts [get_ace_text $ace] }
→ Type: Deny
  User: S-1-5-32-544 (Administrators)
  Rights: write_dac
  Inherited: No
  Include self: Yes
  Recurse containers: No
  Recurse objects: No
  Recurse single level only: No

  Type: Allow
  User: S-1-5-32-545 (Users)
  Rights: 0x00000001
  Inherited: No
  Include self: Yes
  Recurse containers: No
...Additional lines omitted...

For the very common case where you want to grant the same access to a set of accounts, new_restricted_dacl can provide a more convenient interface.

% set dacl [new_restricted_dacl \
              [list System "Network Service" "Local Service"] \
              file_all_access]
→ 2 {{0 0 2032127 S-1-5-18} {0 0 2032127 S-1-5-20} {0 0 2032127 S-1-5-19}}
% foreach ace [get_acl_aces $dacl] {
    puts [get_ace_text $ace]
}
→ Type: Allow
  User: S-1-5-18 (SYSTEM)
  Rights: standard_rights_required, standard_rights_read, standard_rights_write...
  Inherited: No
  Include self: Yes
  Recurse containers: No
  Recurse objects: No
  Recurse single level only: No

  Type: Allow
  User: S-1-5-20 (NETWORK SERVICE)
  Rights: standard_rights_required, standard_rights_read, standard_rights_write...
  Inherited: No
  Include self: Yes
  Recurse containers: No
...Additional lines omitted...

8.3.2. Ordering of ACE’s

By now it should be clear that because access checking is done by examining each ACE in an ACL, the order of ACE’s is crucial. Consider the following hypothetical situation and corresponding ACE’s:

  • Permit all access to Hogwarts for the Order of the Phoenix

  • Deny all access to Hogwarts for Deatheaters

When someone who is a member of both the Order and Deatheaters (are there any such?) attempts to enter Hogwarts, the result will depend the order of these two ACE’s in the ACL.

Now the Windows security subsystem kernel itself does not restrict ordering of ACE’s in any manner. It will happily accept any order of ACE’s in an ACL you place on an resource and when checking access will blissfully check the ACE’s in that order. This implies you are free to order ACE’s in the manner that suits you.

However…​

Various system administration tools, both command line and GUI based like the Windows Shell security editor dialog, expect a convention to be followed regarding the order of ACE’s. They will (re)write existing ACL’s to follow this convention and assume the same in ACL’s when they are displayed.

This convention for ordering ACE’s is

  • all directly applied ACE’s are placed before any inherited ACE’s

  • within each of the above groups, deny ACE’s appear before allow ACE’s

You are well advised to follow this convention when programmatically constructing ACL’s, to ensure correct operation as well as maintain the user’s sanity. The TWAPI commands sort_aces and sort_acl_aces help in this regard to some extent but note that they simply rearrange the order of ACE’s to meet the above convention. The semantics may change in the process so you are best served in constructing the ACL in the appropriate order up front.

8.4. Discretionary Access Control

Let us now look at how Discretionary Access Control (DAC) and its implementation in more detail. As we stated earlier, DAC refers to definition of a set of rules that control the specific types of access allowed to a resource by different security principals based on access rights. These rules are stored in the security descriptor in the form of a Discretionary Access Control List (DACL). When a request is made to access a resource on the behalf of a security principal, the system makes the following sequence of checks to determine whether access should be allowed.

  1. If the security descriptor does not contain a DACL, access is allowed.

  2. Otherwise, each ACE in the DACL is then checked in order.

    1. If the SID in the ACE is not the same as that for the requesting security principal and is not that of a group that contains that principal, the ACE is ignored and the system moves on to the next ACE.

    2. Otherwise (SID match succeeds), the access rights included in the ACE are compared to the requested rights. If there is no overlap, the ACE is ignored and the system moves on to the next ACE.

    3. Otherwise (SID matches and at least one right matches), the ACE type is checked. If it is a deny ACE type, the access is immediately denied without any further checks being done. If it is an allow ACE type, the requested access rights corresponding to the ones in the ACE are explicitly marked as having been allowed. If all requested rights have been explicitly marked as allowed, taking into account those marked in checks against previous ACE’s, the access is allowed with no further checks.

  3. When all ACE’s have been examined and there are still requested rights that are not marked as having been allowed, the request is denied.

In our example above, all ACE’s in the DACL are of type allow. When an access is attempted, if the requested rights are not covered by these ACE’s for the requesting security principal, the request will be denied though there is no explicit deny ACE in the ACL.

Important Make note that having no DACL attached to the security descriptor is not the same as having an empty DACL (an ACL containing no ACE’s) in the descriptor. In the former case, access is always allowed. In the latter case, access is denied as per step 3 above.

There are some special cases that are not dealt with in the above description of access checking. We describe these below.

Owner rights

Each resource is associated with an owner trustee. In Windows versions prior to Vista, if the requester is the owner or belongs to a group that is the owner, checks for the READ_CONTROL and WRITE_DAC rights are passed independent of the DACL contents. Again, other requested rights must be still passed by the DACL checks for the request to be granted.

This behaviour has changed starting with Windows Vista wherein it is now possible to control the resource owner’s ability to read the security information and modify the DACL by adding an appropriate ACE to the DACL. This ACE should use the new built-in OWNER RIGHTS SID S-1-3-4 in conjunction with the READ_CONTROL and/or WRITE_DAC access rights. For example,

% set ace [new_ace deny S-1-3-4 WRITE_DAC]
→ 1 0 262144 S-1-3-4
% get_ace_text $ace
→ Type: Deny
  User: S-1-3-4 (OWNER RIGHTS)
  Rights: write_dac
  Inherited: No
  Include self: Yes
...Additional lines omitted...

Including the above ACE in the security descriptor DACL for the resource will prevent even the owner from modifying the DACL.

Important Using the owner’s actual SID in the above ACE will not have the same effect. That ACE will be ignored as in older Windows versions.
Taking ownership of a resource

If the requester holds the SeTakeOwnershipPrivilege, a check for the WRITE_OWNER access right is always passed irrespective of the DACL. Of course, any other rights requested still go through the DACL access checks. Being able to change the ownership of a resource is important, for example if the original owner leaves the company.

Note the previous discussion of owner rights has no effect for this.

Requesting ACCESS_SYSTEM_SECURITY right

If the request includes the ACCESS_SYSTEM_SECURITY right, the requester must hold the SeAuditPrivilege. Otherwise, the request is denied even if the DACL contents allow it.

Deny-only groups

If the SID in an ACE of type allow is that for a deny-only group, that ACE is ignored. Deny-only groups are intended to be used only to restrict access and therefore should not be used in ACE’s to permit access. The system therefore ignores such ACE’s. We will see an example of their use in User Account Control.

8.5. Mandatory Integrity Control

There is no such thing as perfect security, only varying levels of insecurity.
— Salman Rushdie

Windows Vista introduced support for Mandatory Integrity Control (MIC) which is an additional access control mechanism orthogonal to the discretionary access control we have discussed earlier with DACL’s. In a nutshell, this mechanism associates a levels of ''trust'' with each resource and process and prevents ''less trustworthy'' processes from accessing resources that are marked as to be accessed only from "more trusted" processes.

The access rules implementing MIC are checked before those for DAC. Only if the MIC checks allow access does the system go on to check DACL’s. Otherwise, access is denied.

MIC works as follows:

  • Each security principal and securable resource is assigned an integrity level. The level assigned to a security principal determines its level of access. The level assigned to a resource determines its level of protection. The integrity level is an indication of ''trustworthiness'' with higher values implying a greater level of trust.

  • Access control rules are attached to securable resource’s SACL that indicate what type of access is allowed for security principals that are at a lower integrity level than the integrity level of the resource.

  • When a process is started, it is assigned a security level that is the lower of the level assigned to the security principal on whose behalf it is executing and the level assigned to the executable file from which it was started.

  • When the process attempts access to a resource, the integrity level stored in the process token is checked against that of the resource. If the level of the resource is lower, then the resource’s DACL (if any) is checked and access allowed accordingly. If the resource’s integrity is higher, the integrity rules in the SACL are checked as to whether access is allowed and if so, the DACL checks are executed. Otherwise access is denied.

The rest is just details about how integrity levels and MIC rules are stored.

8.5.1. Integrity levels

Windows defines the integrity levels, each assigned a well-known SID, shown in Integrity labels.

Table 7. Integrity labels
Level Description Name SID

0

Untrusted

Mandatory Label\Untrusted Mandatory Level

S-1-16-0

4096

Low

Mandatory Label\Low Mandatory Level

S-1-16-4096

8192

Medium

Mandatory Label\Medium Mandatory Level

S-1-16-8192

8448

Medium Plus

S-1-16-8448

12288

High

Mandatory Label\High Mandatory Level

S-1-16-12288

16384

System

Mandatory Label\System Mandatory Level

S-1-16-16384

20480

Protected Process

Mandatory Label\Protected Process Mandatory Level

S-1-16-20480

Note Although you can use values for integrity levels other than those defined above, it is probably unwise to do so some tools may not work properly.
Subject integrity levels

The integrity level for a process is stored in its token and can be retrieved with get_token_info. This is referred to as the subject integrity level. This integrity level is the lower of the integrity level associated with the account under which the process runs and the integrity level of the executable image used to run the process.

Most accounts have the Medium integrity level assigned to them. System account and service accounts such as LocalService or NetworkService are assigned the System integrity level. Certain groups used for administrative tasks such as Administrators and Cryptographic Operators are assigned the High integrity level. On the other hand, anonymous users fall into the Untrusted integrity level category.

We can find out what integrity level stored in the access token of a process.

% set tok [open_process_token]
→ 400 HANDLE
% get_token_info $tok -integrity -integritylabel
→ -integrity 12288 -integritylabel high

Alternatively, we can get the same information through the twapi_process module.

% get_process_info [pid] -integrity -integritylabel
→ -integrity 12288 -integritylabel high
Object integrity levels

The above refers to the integrity level for the subject, the process. We now turn our attention to storage of the integrity level associated with the object — the resource — being accessed.

These integrity levels are stored within the System Access Control List (SACL) which is also an ACL attached to a security descriptor. However, the form and function of this ACL is different from what we have discussed so far for DACL’s.

Like DACL’s, SACL’s are also attached to security descriptors and contain a list of ACE’s. However, the ACE’s are not allow or deny ACE’s. ACE entries in a SACL are of the type mandatory_label or audit. Here we only discuss the former which are used for integrity control and postpone the discussion of the latter to the Security auditing chapter.

The integrity level of a resource is discovered by looking at the SACL. If there is no SACL or it does not have an ACE of type mandatory_label, it is defaulted to an integrity level of Medium. Otherwise, the SID in the first mandatory_label ACE in the SACL is examined and the integrity level assigned as shown in Integrity labels.

Let us take a look at the LocalLow subdirectory under our account’s application data directory. First we will print the SACL for the directory.

% set local_low_dir [file join $::env(USERPROFILE) AppData LocalLow]
→ C:/Users/ashok/AppData/LocalLow
% get_acl_text [get_security_descriptor_sacl [get_resource_security_descriptor \
    file $local_low_dir]] -resourcetype file
→ Rev: 2
  ACE #1
    Type: Mandatory_label
    User: S-1-16-4096 (Low Mandatory Level)
    Rights: system_mandatory_label_no_write_up
    Inherited: No
    Include self: Yes
    Recurse containers: Yes
    Recurse objects: Yes
    Recurse single level only: No

We could also get it from the security descriptor, or even easier, directly.

% get_security_descriptor_integrity [get_resource_security_descriptor file \
    $local_low_dir]
→ 4096 system_mandatory_label_no_write_up
% get_resource_integrity file $local_low_dir
→ 4096 system_mandatory_label_no_write_up
% get_resource_integrity file $local_low_dir -label
→ low system_mandatory_label_no_write_up

As we can see, the directory is the Low level integrity which means pretty much everyone can write to it if permitted by the DAC checks. The exception is processes running under Untrusted integrity levels, for example under the Anonymous account.

We see that there is some additional information returned by the above commands. We discuss this next.

Mandatory label policies

We saw in the case of DACL’s that an allow or deny ACE contained rights corresponding to operations for which the ACE was relevant. Similarly, a mandatory_label ACE contains mandatory policies that determine how the integrity controls are applied. The possible values are shown in Mandatory label policies. Note that these policies may be used in combination.

Table 8. Mandatory label policies
Policy name Label Description

NO_EXECUTE_UP

system_mandatory_label_no_execute_up

Processes at lower integrity levels are not allowed to execute the resource, for example a secured COM object.

NO_READ_UP

system_mandatory_label_no_read_up

Processes at lower integrity levels are not allowed to read the resource.

NO_WRITE_UP

system_mandatory_label_no_write_up

Processes at lower integrity levels are not allowed to write to the resource.

Thus in our previous example, we see that Anonymous users would not be able to write to the LocalLow directory but could read from it (again, assuming reading was permitted by any applicable DACL’s).

8.5.2. Default integrity levels

You can explicitly set integrity levels on resources and we will see examples in Security descriptors. However, for the most part applications almost never need to do this and leave it to the system to assign the levels. We describe the manner of these default assignment here.

The first thing to note is that the system does not explicitly assign integrity levels to every type of object. When an explicit integrity level is not assigned, the object is treated as having an implicit integrity level of Medium with a policy setting of NO_WRITE_UP. In particular, the system does not assign explicit integrity levels to files and registry objects when they are created. This is true even if the creating process itself is running at High or System level which means that files and registry keys created by these are treated as being at the Medium level.

For some object types like processes and threads, a mandatory label is explicitly assigned by the system when they are created. When a process or thread is created, the mandatory label assigned to it by the system by default is the integrity level of the creating process.

Note Do not confuse the mandatory label assigned to the process object with the subject integrity level. The latter is stored in the process token and used as the process’s integrity level as a subject that is accessing a resource. The former is stored in the SACL in the security descriptor for the process and is used as the process object's integrity level when some other subject tries to access the process, for example to inject a thread into the process.

The description of default integrity level assignment above is only a very brief summary. For a full description see Windows Vista Integrity Mechanism Technical Reference.

8.5.3. Integrity levels and privileges

To further protect the system, certain privileges can only be held by processes running at a High integrity level. If the process is running at a level below this, the privileges are removed from the process access token even if they are are assigned to the account under which the process is running.

These privileges include SeCreateTokenPrivilege, SeTcbPrivilege, SeTakeOwnership, SeBackupPrivilege, SeRestorePrivilege, SeDebugPrivilege, SeImpersonatePrivilege, SeRelabelPrivilege and SeLoadDriverPrivilege.

8.6. Security descriptors

So far we have described how access control is implemented in terms of ACE’s and ACL’s ''attached'' to secured resources and objects through security descriptors. We now describe this in further detail.

A security descriptor is associated with a resource and ties together various information that controls access to the resource including

  • SID of the owner

  • SID of the primary group

  • DACL attached to the resource

  • SACL attached to the resource

  • various flags that control inheritance, defaults etc.

The subsystem or application implementing the resource is responsible for maintaining the association between the resource and its security descriptor. For example, the NTFS driver will maintain this for files and directories on NTFS systems.

We start off by looking at the contents of a security descriptor.

8.6.1. Reading security descriptors

The get_resource_security_descriptor command is used to retrieve the security descriptor for a resource. This time, instead of using the file system, we will demonstrate using the security configured on the RpcSS Windows service.

% set secd [get_resource_security_descriptor service rpcss -all]
→ 32788 S-1-5-18 S-1-5-18 {2 {{0 0 131205 S-1-5-11} {0 0 917759 S-1-5-18} {0 0 ...

The returned value should be treated as opaque and fields in the security descriptor retrieved using the commands described below. First though, note a few points regarding the get_resource_security_descriptor command:

  • Without any options, the command will retrieve all fields except any audit ACE’s.

  • The above command retrieves all fields in a security descriptor because of the use of the -all option. However, retrieving the audit type ACE’s in the SACL requires the caller to be holding the SeSecurityPrivilege. Instead, you can choose to selectively retrieve only the fields of interest by specifying the appropriate option to the command; for example, specifying options -dacl -owner instead of -all would only retrieve the corresponding fields and would not require the SeSecurityPrivilege.

  • The command requires the type of resource and its name / identifier. However, not all securable resources on Windows, for example an unnamed semaphore, have names that can be passed to get_resource_security_descriptor. Also, in some cases, you may have a handle to the resource but not know its name. In both these cases you can use the -handle option to the command to retrieve the security descriptor.

Reading Owner and Group

The get_security_descriptor_owner and get_security_descriptor_group commands retrieve the SID’s of the owner of the resource and the primary group. In our example, the SYSTEM account owns the RpcSS service.

% get_security_descriptor_owner $secd
→ S-1-5-18

As has been described earlier in Discretionary Access Control, the designated owner for a resource plays an important role in access checks for the resource. On the other hand, the primary group is mostly irrelevant and we do not mention it here. (It is only used in the Posix subsystem which we do not discuss at all.)

Tip The owner in a security descriptor may be a group as well. This permits more than one security principal to gain all the rights reserved for a resource owner. In many cases, resources created by an account in the Administrators group are owned by the group as opposed to the specific account under which it was created.
Reading DACL and SACL

The DACL and SACL fields are retrieved with get_security_descriptor_dacl and get_security_descriptor_sacl and return the corresponding ACL’s stored in the descriptor.

% get_security_descriptor_dacl $secd
→ 2 {{0 0 131205 S-1-5-11} {0 0 917759 S-1-5-18} {0 0 917757 S-1-5-32-544} {0 0...

These can be decoded and manipulated as we described in Working with ACL’s.

% foreach ace [get_acl_aces [get_security_descriptor_dacl $secd]] {
    puts [get_ace_text $ace]
}
→ Type: Allow
  User: S-1-5-11 (Authenticated Users)
  Rights: standard_rights_read, standard_rights_write, standard_rights_execute,...
  Inherited: No
  Include self: Yes
...Additional lines omitted...

There is one point regarding ACL’s that we had made earlier that is worth repeating for emphasis.

Having no DACL attached to the security descriptor is not the same as having an empty DACL (an DACL containing no ACE’s) in the descriptor. In the former case, access is always allowed. In the latter case, access is denied.

The get_security_descriptor_dacl and get_security_descriptor_sacl commands return the string null is returned if the corresponding ACL is not present in the security descriptor. An empty list return values on the other hand indicates the presence of an ACL with no ACE’s in it. Callers must check for these conditions.

Reading descriptor flags

There is additional information present in a security descriptor that can be retrieved with the get_security_descriptor_control command.

% get_security_descriptor_control $secd
→ dacl_present sacl_present self_relative

This returns a set of attributes, the more useful of which are listed in Security descriptor flags. For the remaining, see the TWAPI documentation or Security Descriptors.

Table 9. Security descriptor flags
Flag Description

dacl_present

A DACL is present in the security descriptor.

dacl_protected

Inheritance of ACL’s from the parent resource DACL is disabled.

sacl_present

A SACL is present in the security descriptor.

sacl_protected

Inheritance of ACL’s from the parent resource SACL is disabled.

Reading the integrity level

The security descriptor also holds the integrity level associated with a resource whch can be retrieved with get_security_descriptor_integrity. We have already seen this earlier.

8.6.2. Constructing security descriptors

We now describe how security descriptors are created and modified.

A security descriptor is constructed with the new_security_descriptor command.

% set secd [new_security_descriptor]
→ 0 {} {} null null
% get_security_descriptor_text $secd
→ Flags:
  Owner:  Undefined
  Group:  Undefined
  DACL:
    Rev: null
...Additional lines omitted...

You can specify options to set specific fields to non-default values. Here we construct a DACL as demonstrated earlier and initialize the security descriptor with it.

% set dacl [new_restricted_dacl [list $::env(USERNAME)] file_all_access]
→ 2 {{0 0 2032127 S-1-5-21-604578671-2071180537-1579705201-1001}}
% set secd [new_security_descriptor -dacl $dacl]
→ 0 {} {} {2 {{0 0 2032127 S-1-5-21-604578671-2071180537-1579705201-1001}}} null

Alternatively, we can explicitly set fields in the descriptor after creating it.

% set secd [new_security_descriptor]
→ 0 {} {} null null
% set secd [set_security_descriptor_dacl $secd $dacl]
→ 0 {} {} {2 {{0 0 2032127 S-1-5-21-604578671-2071180537-1579705201-1001}}} null
% set secd [set_security_descriptor_integrity $secd medium [list no_write_up \
    no_read_up]]
→ 0 {} {} {2 {{0 0 2032127 S-1-5-21-604578671-2071180537-1579705201-1001}}} {2 ...
% get_security_descriptor_text $secd
→ Flags:
  Owner:  Undefined
  Group:  Undefined
  DACL:
    Rev: 2
    ACE #1
      Type: Allow
      User: S-1-5-21-604578671-2071180537-1579705201-1001 (ashok)
      Rights: standard_rights_required, standard_rights_read, standard_rights_w...
      Inherited: No
      Include self: Yes
      Recurse containers: No
      Recurse objects: No
      Recurse single level only: No
  SACL:
    Rev: 2
    ACE #1
      Type: Mandatory_label
      User: S-1-16-8192 (Medium Mandatory Level)
      Rights: system_mandatory_label_no_write_up, system_mandatory_label_no_rea...
      Inherited: No
      Include self: Yes
      Recurse containers: No
      Recurse objects: No
      Recurse single level only: No

Of course, the same commands can be used to modify an existing security descriptor after reading it from a resource.

It is not necessary to initialize every field in the descriptor. As we describe next, when the security descriptor is attached to a resource, we can be selective in choosing which fields in the descriptor are to be considered.

8.6.3. Securing resources

We have seen how to create in turn the ACE’s, ACL’s and security descriptors required to protect a resource. One final step remains and that is to attach the security descriptor to a resource. This is done through the set_resource_security_descriptor command which we illustrate through an example.

% set testfile "sectest.tmp"
→ sectest.tmp
% close [open $testfile w]
% set secd [get_resource_security_descriptor file $testfile]
→ 33796 S-1-5-32-544 S-1-5-21-604578671-2071180537-1579705201-513 {2 {{0 16 203...
% get_security_descriptor_text $secd
→ Flags:  dacl_present dacl_auto_inherited self_relative
  Owner:  Administrators
  Group:  None
  DACL:
    Rev: 2
...Additional lines omitted...

Notice that the ACE’s are all inherited.

First let us confirm that we can open the file for reading.

% close [open $testfile r]

Let us set a new security descriptor on the file, one that will completely block access, even for us. We create a new empty DACL (which means no one will be allowed access) and create a new security descriptor based on it.

% set secd [new_security_descriptor -dacl [new_acl]]
→ 0 {} {} {2 {}} null

We then attach it to the file.

% set_resource_security_descriptor file $testfile $secd -dacl -protect_dacl
% set secd [get_resource_security_descriptor file $testfile]
→ 37892 S-1-5-32-544 S-1-5-21-604578671-2071180537-1579705201-513 {2 {}} null
% get_security_descriptor_text $secd
→ Flags:  dacl_present dacl_auto_inherited dacl_protected self_relative
  Owner:  Administrators
  Group:  None
  DACL:
    Rev: 2
  SACL:
    Rev: null

Now let us try opening and closing the file.

% close [open $testfile r]
→ couldn't open "sectest.tmp": permission denied

As expected, we are no longer permitted to open the file.

Let us try a slightly more sophisticated example. We will change the DACL to be readable by anyone in the Users group but not writable by anyone, including us.

% set dacl [new_restricted_dacl [list [well_known_sid Users]] file_generic_write]
→ 2 {{0 0 1179926 S-1-5-32-545}}
% set secd [new_security_descriptor -dacl $dacl]
→ 0 {} {} {2 {{0 0 1179926 S-1-5-32-545}}} null
% set_resource_security_descriptor file $testfile $secd -dacl -protect_dacl

Now we find we can open the file for writing but not reading.

% close [open $testfile w]
% close [open $testfile r]
→ couldn't open "sectest.tmp": permission denied
% get_security_descriptor_text [get_resource_security_descriptor file $testfile] \
    -resourcetype file
→ Flags:  dacl_present dacl_auto_inherited dacl_protected self_relative
  Owner:  Administrators
  Group:  None
  DACL:
    Rev: 2
    ACE #1
      Type: Allow
      User: S-1-5-32-545 (Users)
      Rights: standard_rights_read, standard_rights_write, standard_rights_exec...
      Inherited: No
      Include self: Yes
      Recurse containers: No
      Recurse objects: No
      Recurse single level only: No
  SACL:
    Rev: null

Although the above examples work with DACL’s, the same commands can be used to change other fields in the descriptor as well. However, note that changing other fields, such as the owner, may require the process to be holding certain privileges like SeTakeOwnership.

9. Security auditing

Unlike DAC or MIC, the security auditing facility does not do any enforcement of access policies. Rather it is a means to monitor and log events whenever a secured resource is accessed.

Like MIC, security auditing is implemented through the ACE’s in the SACL attached to a resource, in this case using the audit ACE type. The presence of an ACE of this type causes the system to log an event to the Windows security event log whenever an access check is made for a request for that resource. The ACE can be configured to log an event only when the access is permitted, or only when it is denied, or both.

For example, the following constructs an audit ACE that will log a request for WRITE_DAC access right.

% set ace [new_ace audit Everyone [list write_dac]]
→ 2 192 262144 S-1-1-0
% get_ace_text $ace
→ Type: Audit
  User: S-1-1-0 (Everyone)
  Rights: write_dac
  Audit conditions: success, failure
  Inherited: No
...Additional lines omitted...

The above ACE will result in both allowed and denied accesses being logged. If we instead only wanted denied access being logged, we could create the ACE as

% set ace [new_ace audit Everyone [list write_dac] -auditsuccess 0]
→ 2 128 262144 S-1-1-0
% get_ace_text $ace
→ Type: Audit
  User: S-1-1-0 (Everyone)
  Rights: write_dac
  Audit conditions: failure
  Inherited: No
...Additional lines omitted...

Note that for events to be actually logged to the event log, auditing has to be enabled on the system using the Local Security Policy Editor.

Reading and writing of audit ACE’s and SACL’s and using them in security descriptors is analogous to the corresponding operations for DACL’s so we do not describe them here. However, there is one important difference in that the process must hold the SeSecurityPrivilege for reading or modifying the audit ACE’s in the security descriptor.

10. References

MSDNKNOWNSIDS

Well-known SIDs, Windows SDK documentation.

MSDNAUTHCONSTS

Authorization Constants, Windows SDK documentation.

MSDNSECD

Security Descriptors, Windows SDK documentation.

HOW2007

Writing Secure Code for Windows Vista, Howard, LeBlanc, Microsoft Press, 2007. Details the changes to various security related aspects of Windows in Vista.

MSDNMIC

Windows Vista Integrity Mechanism Technical Reference, MSDN Technical Articles.