Screenshot of the ACL Editor

Figure 1: The ACL Editor

Introduction

In this series of articles, I will discuss the Windows access
control model and how it is implemented in Windows NT and 2000. I
intend this series to be four parts long. I originally intended this
series of articles to be just one part long (I was only going to write
about the Windows-2000 style ACL editor), but there is so much you need
to know about Windows Access control, I might as well devote an entire
series dedicated to the topic.

This first article serves as an introduction to ACL-based
Authorisation (from a programming point of view) in Windows NT and how
the access control model is implemented. If you have never programmed
with Windows security, or are just starting, this first article is for
you. Although this first article will display structures in C, you
should read this article even if you aren’t programming in C. It
contains concepts relevant to all programmers.

This series of articles isn’t meant as a primer on security administration (there are plenty of articles, websites, courses and books on that). Rather, it is intended to discuss the Windows NT Security model from a programmatic
perspective. I will already assume you know how to set permissions on
files, registry keys, and are quite proficient with the ACL editor. I
will also assume you know what users are and what groups are.

Access Control is not available 16-bit Windows or Windows 95/98/ME.

Table Of Contents

The table of contents is for the entire series.

  1. Part 1 – Background and Core Concepts. The Access Control Structures

    • The Security Identifier (SID)
    • The Security Descriptor (SD)
    • The Access Control List (ACL)
    • Windows 2000 Inheritance Model
    • The Token
  2. Part 2 – Basic Access Control Programming
    • Fun with SIDs
    • Getting the Security descriptor for an object
    • Dissecting the Security Descriptor
    • Walking an Access Control List
    • Creating an Access Control List
    • Do I have access?
    • Which Groups are you a member of?
    • Enabling Token Privileges
    • Toy programs download
  3. Part 3.NET – Access Control Programming with .Net 2005
    • Fun with SIDs.NET
    • Getting the Security Descriptor for an object in .NET
    • Dissecting the Security Descriptor in .NET
    • Walking ACLs in .NET
    • Creating ACLs in .NET
    • Access Checks in .NET
    • Group membership.NET
  4. Part 4 – The Windows 2000-style Access Control Editor
    • Features of the ACL Editor
    • Getting Started
    • Implementing the ISecurityInformation Interface.
    • ISecurityInformation::SetSecurity
    • Optional Interfaces
    • Presenting the interface
    • Filepermsbox – A program to display the security descriptor on an NTFS file or folder.

Background

One of the original design goals of Windows NT was to provide a
layer that can implement security for the operating system. The way
Windows NT implemented security for its objects was through the Access
control model. Even role-based security and the .Net classes could not
replace this ACL-based security model. ACLs are far too embedded in the
NTFS architecture objects, and the registry to be rendered obsolete (so
much so, that .NET 2.0 had to relent and end up supporting the ACL
model too).

To start off, we will describe the different types of structures you
may meet when programming for access control. This article is intended
for all programmers (this part contains no code, just structures and
programming concepts).

The Security Identifier (SID)

Before delving into the concepts of authorisation, we should take a discussion about the Security Identifier (SID).
When we humans refer to a user, we refer to it by a user name (like
"Administrator"). A computer, must refer to the user in binary. It
recognises a user by means of a hash (which is called a SID). When a user is created, the computer generates a SID for that user and from then on, it will refer to that user by its SID (not its username). To see a list of your own SIDs, open up regedit and expand the HKEY_USERS tree (you will see a list of the SIDs for currently logged on users). You can convert the raw SID struct into a textual form by using ConvertSidToStringSid().
A textual SID (usually starts with S-1-5-21-…) consists of at least 3
dash-separated values (which stand for revision, authority, sub
authority, ID and primary groups).

A SID can also represent a user group or a computer or a domain or
even a forest. Windows maintains a list of hardcoded SIDs called the
Well-Known SIDs (they represent accounts like the LocalSystem, or the
NetworkService account). You can view a list of these SIDs in the WELL_KNOWN_SID_TYPE enumeration.

To convert a SID to a username, you would need to call LookupAccountSid(), and LookupAccountName() to convert a user name to a SID. However, most of the security functions accept either a SID or a username (they actually accept a TRUSTEE structure, which is a kind of union of the username and SID).

The Security Descriptor (SD)

Windows NT secures its objects by means of a structure called the security descriptor (SECURITY_DESCRIPTOR).
The security descriptor is an integral part of the structure from which
Windows objects are built. This means every object (be it file objects,
registry keys, network shares, mutexes, semaphores, processes, threads,
tokens, hardware, services, drivers…) recognized by Windows NT as an
object can be secured. The security descriptor structure exists in both
kernel mode and user mode (and is identical in both modes). Although
this allows for code reuse for both kernel mode and user mode security,
it also means the SECURITY_DESCRIPTOR inherits some nasty quirks from kernel mode.

If you open up Winnt.h and scroll down to the SECURITY_DESCRIPTOR struct, you’ll see the structure of the security descriptor (Fig. 2). This SECURITY_DESCRIPTOR may actually be one of two structs, one is an absolute security descriptor (SECURITY_DESCRIPTOR), and the other is a self-relative security descriptor (Fig. 3 SECURITY_DESCRIPTOR_RELATIVE).

typedef struct _SECURITY_DESCRIPTOR
{
BYTE Revision; /* currently SECURITY_DESCRIPTOR_REVISION */
BYTE Sbz1; /* 0 */
SECURITY_DESCRIPTOR_CONTROL Control;
/* The type of security descriptor */
PSID Owner; /* points to a SID (the owner SID) */
PSID Group; /* points to a SID (the primary group) */
PACL Sacl; /* An array of discretionary accesses */
PACL Dacl; /* the auditing accesses */
} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;

Figure 2: The Absolute Security Descriptor.

Notice that a security descriptor consists of 5 members (excluding the 2 version control members):

  • Discretionary Access Control List (Dacl): This is where the permissions of the object are kept (who’s allowed access to the object, who’s denied).
  • System Access Control List (Sacl): Specifies the type of auditing to be performed on the object. If an auditing event occurs, it will be stored in the Auditing Event Log.
  • Owner: Specifies the owner of the object (as a
    SID). The owner of the object can always alter the security
    descriptor, regardless if someone else has locked out access.
  • Group: Specifies the primary group of the
    object (as a SID). Windows mostly ignores this parameter (it was for
    POSIX compatibility, but its presence is rather vestigial now).
  • Control: A bunch of flags that make up a 16-bit integer. It can be zero or more of the following flags:
    • SE_DACL_PRESENT: The Dacl member is valid.
    • SE_SACL_PRESENT: The Sacl member is valid.
    • SE_DACL_AUTO_INHERITED: The DACL auto-inherits and gets entries from its parent included in itself.
    • SE_SACL_AUTO_INHERITED: same as SE_DACL_AUTO_INHERITED, but it applies to the SACL.
    • SE_DACL_PROTECTED: If the parent’s ACLs conflict with any ACL defined here, override the parent’s ACL. Useful for overriding inheritance.
    • SE_SACL_PROTECTED: Same as SE_DACL_PROTECTED, but for SACLs.
    • SE_DACL_DEFAULTED: The Dacl member equals the default DACL for this object type.
    • SE_SACL_DEFAULTED: The Sacl member equals the default SACL for this object type.
    • SE_GROUP_DEFAULTED: The Group member equals the default Group for this object type.
    • SE_OWNER_DEFAULTED: The Owner is defaulted.
    • SE_SELF_RELATIVE: Indicates this security descriptor is self-relative.

The last 4 entries of this structure are all pointers, and they
point to the buffers where the ACLs etc. can be found. These pointers
can point to any valid location in memory (not necessarily in a
contiguous block). Because the security descriptors aren’t contiguous,
it is going to be rather cumbersome to write the security descriptors
to disk, or across processes. Microsoft solved this problem by
introducing a new structure, called the self-relative security descriptor (Fig. 3 SECURITY_DESCRIPTOR_RELATIVE)

/* This is not valid C or C++ code. */
typedef struct _SECURITY_DESCRIPTOR_RELATIVE
{
BYTE Revision; /* currently SECURITY_DESCRIPTOR_REVISION */
BYTE Sbz1; /* 0 */
SECURITY_DESCRIPTOR_CONTROL Control;
/* The type of security descriptor */
DWORD OwnerOffset;
/** The index of this->Owner. ie.
(this+OwnerOffset) should == this->Owner **/

DWORD GroupOffset; /* The index of this->Group */
DWORD SaclOffset; /* The offset to Sacl */
DWORD DaclOffset; /* The offset to Dacl*/

struct SecurityDescriptorData
{
/* The data for the security descriptor is after the 4 DWORDs */
... /* padding bytes */
SID Owner; /* The SID is stored Inside the structure */
...
SID Group; /* so are the other parts */
...
ACL Sacl[];
/* the entries need not appear in this order */
...
ACL Dacl[];
} ;
} SECURITY_DESCRIPTOR_RELATIVE, *PISECURITY_DESCRIPTOR_RELATIVE;

Figure 3: The structure of the relative security descriptor

The self-relative security descriptor is much more complicated than
the absolute security descriptor (so complex, that you cannot represent
it in C++). Although the first 3 fields are the same as the absolute
security descriptor, the security descriptors become very different
after that. Following the Control member are 4 DWORDs (which represents offsets to the data). The data for the security descriptor all follow these 4 DWORDs.
I’ve inserted padding bytes into the structure to illustrate that the
data can appear anywhere in the buffer (simply change the Offset
members to move the buffer). The 4 members do not need to appear in any
particular order either. By choosing special offsets, you can make the Owner member appear after
the group. At the expense of being horrendously complicated, the
self-relative security descriptor occupies one contiguous block, making
it suitable for transporting between application boundaries and storage
media.

How can you tell the difference between an absolute and self
relative security descriptor? The difference is that with an absolute
security descriptor, after the Control member are 4 pointers
to the Group/Owner/SACL/DACL (in that order), and these
4 pointers point to separate locations in memory where the data can be
found. With a self relative security descriptor, after the Control
member are 4 DWORDs which represent offsets to the
Group/Owner/SACL/DACL. For example, if the group member has
the value 0xf, then it’s telling Windows that "You can find the group SID 0xf bytes after me". 64-bit Windows makes this even more confusing when these two structures have differing sizes!

To make matters worse, Microsoft doesn’t distinguish between the
absolute or self-relative security descriptor in their docs. Microsoft
have reserved the right to make internal changes to the security
descriptor, meaning you cannot rely on this structure being the same in
future. Instead, if you want to manipulate a security descriptor, you should do so using the authorisation APIs.
Using the APIs encapsulate the complexity of these structures from you.
So to distinguish between an absolute security descriptor and a
self-relative one, call GetSecurityDescriptorControl(), and test for the SE_SELF_RELATIVE flag.

It’s important that you know which API expects an absolute security
descriptor, and which ones expect a self-relative one. In Part 2 of
this series, we will actually build security descriptors.

The Access Control List (ACL)

Whenever you perform an action (eg. a read) on an object in Windows NT, the action is encoded into a 32 bit integer (called the ACCESS_MASK). The ACCESS_MASK is specific to the object you are trying to create (if you read a file, the ACCESS_MASK would be FILE_GENERIC_READ). When you open the object with the requested ACCESS_MASK, Windows will get your username (inside your thread token) and then start reading the discretionary access control list (obtained from the security descriptor).

The DACL can be thought of as a table of user SIDs, ACCESS_MASKs, and access types. Don’t try to code it as an array of structs though. Use the low-level ACL functions GetAce(), GetAclInformation() & friends if you want to parse an ACL. In NT4, Microsoft provides a view of the ACL that looks exactly like a table of SIDs, ACCESS_MASKs and types (the EXPLICIT_ACCESS structure).

/* Not valid C or C++ code */
struct ACE
{
BYTE AceType; /* can be allow/deny/audit/custom */
BYTE AceFlags; /* Inherited? */
...
ACCESS_MASK Mask; /* Specifies the action */
...
SID Sid; /* associate with this user/group/... */
} ;

struct ACL
{
BYTE AclRevision;
...
WORD AceCount; /* number of ACEs */
...
ACE AceList[this->AceCount]; /* An array of ACEs */
} ;

/* You can think of an ACL as an array of EXPLICIT_ACCESSes */
typedef struct _EXPLICIT_ACCESS
{
DWORD grfAccessPermissions; /* ACCESS_MASK */
ACCESS_MODE grfAccessMode; /* Allow/deny/audit/custom/... */
DWORD grfInheritance; /* Inherited? */
TRUSTEE Trustee; /* The SID */
} EXPLICIT_ACCESS, *PEXPLICIT_ACCESS;

Figure 4: An outline of the ACL and the ACE.

If the DACL is a table, then each row in the table is called an
Access Control Entry. When an action is performed, Windows will
enumerate this list of ACEs to find an entry that refers to you (you being the thread token). Windows enumerates the ACEs in the order they appear in the ACL.
At first sight this goes against your intuition, the Help
documentation, and what the MCP books say ("I thought Windows walks the
Deny ACEs first, then walks the Allow ACEs. Now you’re telling me this
is wrong?"). Actually, we are both right. When Windows sets the DACL,
it sorts the access control entries so that deny ACEs do precede allow ACEs, and the access control model follows what you were taught.

If you make a call to the native functions, you can circumvent the
sorting, and make the ACEs appear in any order you like. The Cygwin
tools utilise this technique to create DACLs unordered. However, these
DACLs will not obey the access control rules, and you can make files
with broken ACLs.

If you (your token) aren’t found in the ACL, then the open object function fails (Access denied). If you are found, Windows will look in the ACE to see what you are allowed to do. If the ACE allows you to open the object, you are granted access. If anything fails here, you are denied access (this is an example of a security best practice: "if anything goes wrong, make sure you fail securely").

Now that you have been granted or denied access, Windows will now
make a check on the other ACL, the System Access Control List. The
difference between an SACL and a DACL is that DACL contains allow/deny
entries, but an SACL contains audit entries. The SACL tells Windows
which actions to log in the Security Event Log (audit
successes/failures). Apart from that, you can treat the SACL/DACL as
the same. If Windows finds an SACL which tells it to audit the access,
then the access attempt is written to the Event log.

If you want to see if an ACL allows you access to the object, use the AccessCheck() API on the object.

Creating a good discretionary access control list.

In almost
every case, Windows has already decided a good discretionary access
control list for you. When you encounter an API that asks for a
security descriptor (or a SECURITY_ATTRIBUTES structure), simply pass in NULL for this parameter. Passing in NULL for either the SECURITY_ATTRIBUTES or SECURITY_DESCRIPTOR will tell the system to apply the default security descriptor. And that is probably what you were after.

If you need more advanced security, make sure you create a security
descriptor that has a filled DACL. If you’ve initialized a security
descriptor, but have forgotten to build up a DACL (and attach it to the
security descriptor), you will get a NULL DACL. Windows sees this NULL DACL as an object that has the following ACE:

"Everyone: Full control"

Therefore, Windows will allow access to the object regardless of the
action. This is one case where Microsoft broke its own best practices.
When it encounters something unexpected, Windows should fail securely.
In this case it doesn’t. So with a NULL Dacl, everyone
(including malware) will be able to do anything to the object
(including setting a nasty rootkit-style DACL). For this reason, don’t create security descriptors with NULL DACLs.

Setting a NULL DACL isn’t the same as setting a NULL Security descriptor. When passing a NULL
security descriptor, Windows will replace it with a default security
descriptor, and this security descriptor is secure, and allows enough
access to the object. Setting a NULL DACL means passing a valid security descriptor, whose Dacl member happens to be NULL.

In a similar way, you probably don’t want to set a DACL that has
doesn’t contain any Access Control entries. In this DACL, when Windows
attempts to enumerate the access control list, it will fail on the
first attempt, therefore, it will deny access regardless of the action.
The result is a completely inaccessible object (not much better than a
non-existent object). You must have a filled DACL if you want to access
the object.

So how do we build a Discretionary access control list?

Think about who you want to allow access to and who you don’t want
to allow access. Is your DACL black list based (full of deny ACEs), or
white list based (full of allow ACEs). If you are creating an ACL, you
should prefer a white list approach to the security descriptor. How
long is the object going to last? Is it a long running object (like the
app mutex for a long running process), a short running object (like a
synchronization event), or is it a permanent object (like a file)?

To determine a good security descriptor decide what you are going to
do the object. If you are securing a kernel object, you probably only
need to synchronize, read and read the security on it. If you are
securing a file, you’ll probably need all accesses available (one day).

Generally, for permanent objects you need at least two accounts to
have access to the object. The first user is the LocalSystem account
(restricting access to this account can lead to serious problems if it
is a permanent object). The other is the object creator (or
alternatively, the current owner) of the object. You should also
consider giving administrators full control to the object. Another
technique you can consider is only allowing the users read and execute
access. When they want to delete the file, they will have to add the DELETE right themselves, then delete it.

For short running objects, you should apply the principal of least
privilege to the object. For example, if all you are going to do with
an object is read from it and synchronize it, then create a DACL that
only allows read and synchronize access (you can even restrict the
LocalSystem’s account this way if it is a short lived object).

If your object supports inheritance, then you don’t need a special
DACL on the object (unless your file is special in terms of security),
you can just apply the DACL for the parent (do this by setting the auto
inherit flag in the Control member of the security descriptor). The default security descriptor will apply the inheritance flag for you.

And finally, if in doubt, ask the admins!

Windows 2000 Inheritance Model

Starting with Windows 2000, ACLs can be set to inherit from their
parent. What this means is that the ACL of the parent will be applied
to the child (eg. the permissions for "\Program Files\Common
Files\Microsoft Shared" will be the same as "\Program Files\Common
Files"). A special flag in the ACE will state that it is inherited.

As stated in the previous section, if Windows walks the list of ACEs
until it reaches the end or finds an ACE that matches you. If
inheritance is enabled, when Windows reaches the end of the list, it
will start walking the ACL of the parent folder to find a matching ACE.
Therefore, the object will have the parent’s ACL applied to itself,
automatically. This only occurs if the ACL is set to inherit from the
parent object.

If the folder also has inheritance, Windows will walk up that folder
too. This can continue all the way until Windows hits a folder that has
inheritance disabled, or it hits the root of the drive. The result is
that the resultant ACL is a merged view of the current object, and its
parent. The ACEs that come from the parent are called inherited ACEs, and the ACEs that come from the file itself are called the protected ACEs.

To support ACL inheritance, you must design your classes to have
parent-child relationships (like a file/folder, or a registry
key/value). The parent will be referred to as a container (eg. folder),
and the child will be referred to as an object (like a file).

For really advanced ACL control Windows 2000 supports applying ACEs
only to folders and not files (and vice versa). The ACE flag that
controls inheritance also states which type of inheritance to apply.
There are four flags:

  1. OBJECT_INHERIT_ACE (OI): indicates the ACE gets inherited to child objects (eg. files).
  2. CONTAINER_INHERIT_ACE (CI): indicates the ACE gets inherited to child containers (eg. subfolders).
  3. INHERIT_ONLY_ACE (IO): indicates the ACE applies not to this object, but to child objects (eg. child files/subfolders only).
  4. NO_PROPAGATE_INHERIT_ACE (NP): do not apply this ACE to grandchildren, just the direct children (eg. apply files but not files in subfolders).

These flags can be (and usually are) combined. To make the ACE apply
to every child object within yourself (including inside subfolders),
specify (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE).

For a more complete description of ACE inheritance, check out Keith Brown’s book.

The Access Token

The final structure I will describe today is the Access token. I
don’t want to delve too much into this structure otherwise I may stray
off topic (it’s relevant not only to access control, but to privileges,
policy, group membership as well as IPC across logon sessions). The
access token forms an integral part of both the thread and process
objects. The access token identifies who you are in the context of
security. Windows NT maintains user information per process, as this
allows processes and threads to run as a different user (if you wanted
to know how Task Manager knows which user a process runs under, it’s in
the access token). The access token is the feature that allows
services, runas, and fast user switching to exist.

The access token can either be a process token, a primary thread
token, or a thread impersonation token. Unless your thread is
impersonating, your thread doesn’t have a token (you’ll be using the
process token instead). Once you’ve made your thread impersonating,
you’ll have an impersonation token associated with your thread. You can
convert your impersonation token into a primary token (suitable for CreateProcessAsUser()) by calling DuplicateTokenEx()).

When you log in to Windows, your processes will have their tokens
set to your username when run. If you open your own token, and look
inside it, you can find out the name of the user you are running as
(all without needing to invoke GetUserName()).

Screenshot of the Graphical Whoami App In Process Explorer

Figure 5: An illustration of the access token.

There’s a lot of information you can gleam from a token via GetTokenInformation(). For example:

  • TokenUser: Which user the process runs as.
  • TokenGroupsandPrivileges: Which groups the user belongs to (as well as TokenGroups, TokentRestrictedSids, TokenPrivileges And TokenUser).
  • TokenGroups: The complete list groups to
    which the user belongs (gives more information than "net.exe user
    <user>", User Management console, and "control userpasswords2").
  • TokenPrivileges: The list of privileges.
  • TokenDefaultDacl, TokenOwner, TokenDefaultGroup: The definition of a default security descriptor.
  • TokenType: whether this token impersonates.
  • TokenSource: the source of the access token.
  • TokenRestrictedSids: The list of restricted SIDs.

For the purposes of access control, most of the information we need from the access token are in the TokenGroupsandPrivileges structure. With this value, you rollup the GetUserName(), NetUserGroups(), CheckTokenMembership() and LookupPrivilegeValue()
APIs all in one. The token shows that being a member of the
administrators group also makes you a member of the None group, the
Authenticated Users group, the Users group, the Power Users group, and
the LOCAL group. This is useful to you as you now have a list of user
groups to which you belong, and can now search an ACL using this list.
To see if an ACL grants you access:

  1. Open up your thread token.
  2. Get the list of groups.
  3. Obtain the SECURITY_DESCRIPTOR for the object.
  4. Get the ACL from the security descriptor.
  5. For each ACE in the ACL, lookup the SID associated with that ACE.
  6. Lookup this SID in the list of groups you created in step 2.
  7. If found, see if it is a deny or allow ACE.
  8. If it is an allow ACE, compare the ACCESS_MASK with your desired ACCESS_MASK.
  9. If all accesses are allowed, you are allowed access.
  10. If an error occurs, you are denied access.

Add in error/parameter checking to the above list (and auditing), and you have just created yourself a pseudo AccessCheck() function.

You’ve probably already encountered the default security descriptor. Whenever you encountered an API that requested a SECURITY_ATTRIBUTES parameter, you most likely passed in NULL for that parameter (which means "default security descriptor"). By calling GetTokenInformation(TokenDefault*), you can find out what that default security descriptor is.

The privileges are a list of policies that must be enabled before
you are allowed to perform certain system-critical functions (like
shutdown the system, or install drivers). These privileges are
configurable via group policy (user rights assignment), and are listed
in your thread token. The privileges are disabled by default
(exception: SeChangeNotifyPrivilege), so you must enable the privilege before you can use it. The Platform SDK provides a sample function SetPrivilege()
that can enable and disable a privilege whenever you need it. I stongly
suggest you add this function to your security utility library, because
you are going to reuse that function again and again, especially with
access control. Enabling a privilege then becomes a matter of adding
two lines to your code:

...
SetPrivilege(SePrivilegeName, TRUE);

/* ... Use privilege ... */

SetPrivilege(SePrivilegeName, FALSE);
...
Figure 16: Enabling and disabling a privilege using the Platform SDK sample function.

I don’t want to go into sandboxing or impersonation (since they are
rather off topic for access control). If you want to know more about
impersonation and privileges, look at this WindowsSecurity article, or Paul Cooke’s book.

A note on the Security Descriptor Definition Language

The Security Descriptor Definition Language is an attempt to
represent a security descriptor in text form that both administrators
and programs can understand. The full reference (and IMHO, the only
decent reference) to the SDDL format is located in the help
documentation.

Briefly, the SDDL string contains the following parts: a group, an
owner, the DACL and SACL. These sections are delimited by a one-letter
label followed by a colon (O: delimits the owner, G: delimits the group, D: delimits the DACL, S: delimits the SACL). Following the group and delimiters are SIDs that denote the owner and group. Following the D: and S: delimiters are a list of bracket separated entries (each bracket represents an ACE in the list).

The ACE bracket consists of six semicolon delimited terms that represent ACE type, Inheritance, ACCESS_MASK,
GUID, inherited GUID, and user SID respectively. The ACE brackets are
stacked up in the list to form the overall ACL. Prefixing the list of
ACE brackets can be some other flags; these represent the control flags
for the security descriptor. The following is an example SDDL we will
dissect (I’ve separated the different sections for clarity):

O:AOG:DAS:D:(A;;RPWPCCDCLCSWRCWDWOGA;;;S-1-0-0)(A;;GA;;;SY)

First, tokenize this string using the delimiters:

O: G: S: D:
AO DA   (A;;RP WP CC DC LC SW RC WD WO GA;;;S-1-0-0) (A;;GA;;;SY)
Figure 7a: The SDDL string tokenized into separate strings.

Next, expand the SID strings and ACE strings:

Owner Group SACL DACL
Account Operators Domain Administrators   (A;;ADS_RIGHT_DS_READ_PROP | ADS_RIGHT_DS_WRITE_PROP |
ADS_RIGHT_DS_CREATE_CHILD | ADS_RIGHT_DS_DELETE_CHILD |
ADS_RIGHT_DS_LIST | ADS_RIGHT_DS_SELF | READ_CONTROL | WRITE_DAC |
WRITE_OWNER | GENERIC_ALL;;;
Null Sid)(A;;GENERIC_ALL;;;LocalSystem)
Figure 7b: The SDDL string with the SID strings and ACE strings expanded

Finally, expand the constant strings:

Owner Group SACL DACL
Account Operators Domain Administrators   Allow (Null Sid): 0×01e000bb
Allow LocalSystem: 0×100000
Figure 7c: The security descriptor that the SDDL string specifies.

This is the meaning of the SDDL string specified. For more practice using SDDL strings, take a look at the %windir%\security\templates\*.inf
files. The Inf files contain a whole bunch of SDDL strings. This is
what Windows Setup used to apply the default security descriptors for
your Windows installation. See if you can decrypt the security
descriptors for those strings. (to find out the answers, you can call
the ConvertStringSecurityDescriptorToSecurityDescriptor() function on the security descriptor strings).

Sorry about the poor explanation about SDDL strings. Like I said,
the best reference for SDDL is in the SDK documentation, and I still
stand by that statement.

Coming Up

In this part, we learnt how Windows NT secured its objects using
security descriptors. We discovered that Windows recognizes users,
groups and computers as SIDs. We learnt that security descriptors
originally came from kernel mode, therefore they can be complex beasts.
We were shown the parts of a security descriptor, the five components
which make it up, and how it is stored in memory. We were then shown
the two types of access control list. We discovered the structure of
the ACL, how Windows reads an ACL, and how inheritance is implemented
within the Access control model. The last structure we learnt about was
the access token. We were taught what information can be read from
token, and finished off with enabling a permission.

In Part 2 we will start writing some example code for reading and
writing security descriptors. We will also obtain information from the
primary token to create a WhoAmI clone. Whilst you are waiting for the
next article to come up, I want you to choose which technique you are
going to program with. I am going to present 4 ways of programming with
security:

  1. The low level way. If you need to program for NT3.5, and don’t have
    ATL, you’ll have no choice but to choose this method. This method makes
    direct calls to the low level ACL functions, and they are Horrible! Not
    only is it nontrivial to program, this method is also excessively
    complex and error prone (no wonder Windows NT had so many security bugs!). Unless you need to support backward compatibility (since when did you care about backward compatibilty?),
    I strongly recommend you steer clear of this method if you can help it.
    If you want a preview of this method, check out the SDK docs on SetEntriesInAcl().
  2. The Windows 2000 way. Realising how difficult method 1 was,
    Microsoft invented an alternative API for creating security
    descriptors, the Security Descriptor Definition Language (SDDL).
    Initially, SDDL may not be much better than low level authorisation
    (just as complex, and not as compatible). However, being in text mode
    makes SDDL infinitely more readable, for both programmers AND
    administrators. And if you look closely at a number of SDDLs, along
    with the corresponding ACLs, you’ll soon get the hang of it. Windows
    2000 also introduces API helpers to automate repetitive tasks (like
    printing a SID in text form). If you want a preview, check out the SDK
    docs on ConvertStringSecurityDescriptorToSecurityDescriptor().
  3. The ATL Way. As part of the Trustworthy Computing Initiative,
    Microsoft made a number of updates to ATL, culminating in the ATL
    security classes. If you have Visual C++.Net, and don’t mind
    programming with ATL C++, you’ll want to choose this method. To see a
    preview, check out the CSecurityDesc() class from the Visual C++ help.
  4. The .Net Way. Starting with v2.0 of the .Net Framework, you
    can also choose to program security descriptors in a fully object
    oriented managed environment. Note, that I will not cover this in part
    2 of the series. Instead programming security in .Net will have its
    very own part in this series (Part 3). If you have Visual Studio .Net
    2005, you should strongly consider choosing this method. You can see a
    preview in the System.Security.AccessControl (available in the MSDN beta docs).

Bibliography/Further Reading

I obtained most of the material for this article from the books

Writing Secure Code by Michael Howard, published by MS Press.
Windows Internals 4th Edition by Mark Russinovich.
The .Net Developers Guide To Windows Security by Keith Brown.
Windows 2000 Security by Paul Cooke.
The Authorisation SDK (part of the Platform SDK).
The Platform SDK Sample programs.

There are also many good articles out there on the internet; here’s some starters:

Larry Osterman’s blog.
Raymond Chen’s blog.
Robby Manderson’s Article.
Peter Kenyon’s Article.
Windows NT Security Part 2

If you want test programs:

Zhefu Zhang’s RunAsEx.
Sysinternal’s Process Explorer, AccessEnum, TokenMon, and WinObj.
The Microsoft Resource Kit contains many useful tools to administrate ACLs, Security descriptors, and more . . .
Helge Klein’s SetAcl.


1条评论

  1. I have a case now.

    use VBScript to query the system for the current security settings for the specified file or folder. Then compare the current security settings against the policy settings (defined in SDDL).

    policy settings like, "D:PAR(A;;FA;;;BA)(A;;FA;;;SY)" or "D:PAR(A;OICI;FA;;;BA)(A;OICIIO;FA;;;CO)(A;OICI;FA;;;SY)(A;OICI;0×1200a9;;;BU)S:AR(AU;OICIFA;FA;;;WD)"

    I don’t know how to compare.

    Could you help me? Thanks

    email: alan.zhou@timogen.com

发表评论

评论也有版权!

click to change验证码