|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Figure 1: The ACL Editor IntroductionIn this series of articles, I will discuss the Windows Access Control model and its implementation 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 Authorization (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 in 16-bit Windows or Windows 95/98/ME. Table Of ContentsThe table of contents is for the entire series.
BackgroundOne 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 was to be rendered obsolete (so much so, that .NET v2.0 had to relent and end up supporting the ACL model too). To start off, I 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). 1. The Security Identifier (SID)Before delving into the concepts of authorization, I should take a discussion about the Security Identifier (SID). We humans like to refer to a user by their 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 A SID can also represent a user group, a computer, 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 To convert a SID to a username, you would need to call 2. The Security Descriptor (SD)Windows NT secures its objects by means of a structure called the security descriptor ( If you open up Winnt.h and scroll down to the 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 five members (excluding the two version control members):
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 /* 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 three fields are the same as the absolute security descriptor, the security descriptors become very different after that. Following the 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 To make matters worse, Microsoft doesn't distinguish between the absolute or self-relative security descriptor in their docs. Microsoft has 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 authorization 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 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, you will get around to actually building a security descriptor. 3. The Access Control List (ACL)Whenever you perform an action (e.g. a read) on an object in Windows NT, the action is encoded into a 32 bit integer (called the The DACL can be thought of as a table of user /* 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 low level 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) isn'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 4. Creating a good discretionary access control listIn 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 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 "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 Setting a In a similar way, you probably don't want to set a DACL that 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 to 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 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 And finally, if in doubt, ask the admins! 5. Windows 2000 Inheritance ModelStarting 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 (e.g. 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, 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 (e.g. 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:
These flags can be (and usually are) combined. To make the ACE apply to every child object within yourself (including inside subfolders), specify For a more complete description of ACE inheritance, check out Keith Brown's book. 6. The Access TokenThe 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 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
Figure 5: An illustration of the access token. There's a lot of information you can gleam from a token via
For the purposes of access control, most of the information we need from the access token are in the
Add in error/parameter checking to the above list (and auditing), and you have just created yourself a pseudo You've probably already encountered the default security descriptor. Whenever you encountered an API that requested a 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: ... 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. 7. A note on the Security Descriptor Definition LanguageThe 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 in my humble opinion, 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 ( The ACE bracket consists of six semicolon delimited terms that represent ACE type, inheritance, O:AOG:DAS:D:(A;;RPWPCCDCLCSWRCWDWOGA;;;S-1-0-0)(A;;GA;;;SY)
First, tokenize this string using the delimiters:
Figure 7a: The SDDL string tokenized into separate strings. Next, expand the SID strings and ACE strings:
Figure 7b: The SDDL string with the SID strings and ACE strings expanded Finally, expand the constant strings:
Figure 7c: The security descriptor that the SDDL string specifies. This is the meaning of the SDDL string specified. For a whole bunch of example SDDL strings, take a look at the %windir%\security\templates\*.inf files. 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 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. 8. Coming UpIn this part, you learnt how Windows NT secured its objects using security descriptors. You discovered that Windows recognizes users, groups and computers as SIDs. You learnt that security descriptors originally came from kernel mode, therefore they can be complex beasts. You were shown the parts of a security descriptor, the five components which make it up, and how it is stored in memory. You were then shown the two types of access control list. You discovered the structure of the ACL, how Windows reads an ACL, and how inheritance is implemented within the Access control model. The last structure you learnt about was the access token. You were taught what information can be read from a token, and finished off with enabling a permission. In Part 2, you will start writing some example code for reading and writing security descriptors. You 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 four ways of programming with security:
HistoryThe history and bibliography are now maintained in Part 4 [^].
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||