Copyright © 2020 Ashok P. Nadkarni. All rights reserved.
1. Introduction
Distrust and caution are the parents of security.
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.4.1
% package require twapi_account
→ 4.4.1
% package require twapi_process
→ 4.4.1
% package require twapi_os
→ 4.4.1
% namespace path twapi
2. Concepts
We first introduce some basic concepts and terminology we will use in the rest of this chapter.
A security principal is an entity whose identity can be authenticated. It may represent a real person, or a system.
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.
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.
id_security_trustee
.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.
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.
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. |
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-2879233261-3835993386-4047337184-500
% lookup_account_sid $sid
→ Administrator
Both will optionally retrieve some additional information about the account.
% lookup_account_name Administrator -all
→ -domain IO -type user -sid S-1-5-21-2879233261-3835993386-4047337184-500
% lookup_account_sid $sid -all
→ -domain IO -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 theuser1
account are different even though both are looked up on the same system. The former is a local account defined on thevm3-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.
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-2879233261-3835993386-4047337184-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.
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-2879233261-3835993386-4047337184-1001
% lookup_account_sid $sid
→ ashok
% map_account_to_sid $sid
→ S-1-5-21-2879233261-3835993386-4047337184-1001
% map_account_to_sid $name
→ S-1-5-21-2879233261-3835993386-4047337184-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-2879233261-3835993386-4047337184
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-2879233261-3835993386-4047337184-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-2879233261-3835993386-4047337184-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-06da9525
% new_luid
→ 00000000-06da9526
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.
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 [email protected] and
[email protected] .
|
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.
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$}
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
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...
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.
|
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$}
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}
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$}
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
→ The user name could not be found.
% get_user_local_groups VMAPN\\vm2admin
% get_user_local_groups VMAPN\\vm2admin -recurse 1
→ Administrators Users
Unqualified name | |
Qualified name | |
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}
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
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.
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}
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 [sect_security_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]
→ 272 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.
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-2879233261-3835993386-4047337184-1001
% get_token_user $tok
→ S-1-5-21-2879233261-3835993386-4047337184-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
orCONSOLE 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 [sect_security_logon_sessions].
The only group attribute we discuss here is use_for_deny_only
id_security_deny_only_group
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 [sect_security_uac].
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-06d8eac9
modificationluid = 00000000-06a13782
% enable_token_privileges $tok SeShutdownPrivilege
→ SeShutdownPrivilege
% print_dict [get_token_statistics $tok] luid modificationluid
→ luid = 00000000-06d8eac9
modificationluid = 00000000-06da954c
% disable_token_privileges $tok SeShutdownPrivilege
→ SeShutdownPrivilege
% print_dict [get_token_statistics $tok] luid modificationluid
→ luid = 00000000-06d8eac9
modificationluid = 00000000-06da954d
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.)
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-0007db1e
% dict get [get_token_statistics $tok] authluid
→ 00000000-0007db1e
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.
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...
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.
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 todisable_privileges
. The problem in doing this is that one of the privileges passed toenable_privileges
might already be enabled in the token. Then when the privilege is passed intodisable_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 {
} finally {
disable_privileges $newly_enabled_privs
}
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 {
} {SeChangeNotifyPrivilege SeShutdownPrivilege}
Make your privileged calls |
which is a more convenient interface.
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.
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.
The standard rights applicable to all resource types are shown in Standard rights.
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, 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.
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.
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 beforeallow
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.
-
If the security descriptor does not contain a DACL, access is allowed.
-
Otherwise, each ACE in the DACL is then checked in order.
-
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.
-
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.
-
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 anallow
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.
-
-
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.
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.
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.
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. |
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.
ACCESS_SYSTEM_SECURITY
rightIf 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.
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 [sect_security_uac].
8.5. Mandatory Integrity Control
There is no such thing as perfect security, only varying levels of insecurity.
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.
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. |
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]
→ 772 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
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 sect_security_sacl 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.
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.
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.
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
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 theaudit
type ACE’s in the SACL requires the caller to be holding theSeSecurityPrivilege
. 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.
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.)
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.
|
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.
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.
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-2879233261-3835993386-4047337184-1001}}
% set secd [new_security_descriptor -dacl $dacl]
→ 4 {} {} {2 {{0 0 2032127 S-1-5-21-2879233261-3835993386-4047337184-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]
→ 4 {} {} {2 {{0 0 2032127 S-1-5-21-2879233261-3835993386-4047337184-1001}}} null
% set secd [set_security_descriptor_integrity $secd medium [list no_write_up \
no_read_up]]
→ 20 {} {} {2 {{0 0 2032127 S-1-5-21-2879233261-3835993386-4047337184-1001}}} {...
% get_security_descriptor_text $secd
→ Flags: dacl_present sacl_present
Owner: Undefined
Group: Undefined
DACL:
Rev: 2
ACE #1
Type: Allow
User: S-1-5-21-2879233261-3835993386-4047337184-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-2879233261-3835993386-4047337184-1001 {2 {{0 16 2...
% get_security_descriptor_text $secd
→ Flags: dacl_present dacl_auto_inherited self_relative
Owner: Administrators
Group: ashok
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]]
→ 4 {} {} {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-2879233261-3835993386-4047337184-1001 {2 {}} null
% get_security_descriptor_text $secd
→ Flags: dacl_present dacl_auto_inherited dacl_protected self_relative
Owner: Administrators
Group: ashok
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]
→ 4 {} {} {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: ashok
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.