Creating ACLs

Creating ACLs

I'm covering the creation of ACLs because one of the arguments I hear from developers against adding ACLs to their applications is that they have no idea which APIs to use. In this portion of the chapter, I'll delve into creating ACLs in Windows NT 4 and Windows 2000, and I'll explore some new functionality in Visual Studio .NET and the Active Template Library (ATL).

Creating ACLs in Windows NT 4

I remember the first time I used ACLs in some C++ code, and it was daunting. At that point I realized why so many people don't bother creating good ACLs it's a complex task, requiring lots of error-prone code. If it makes you feel any better, the following example code is for Windows NT 4 and later. (The code for versions of Windows NT prior to version 4 would be even more complex, involving calls to malloc and AddAce!) The code shows how to create an ACL and, in turn, a security descriptor, which is then applied to a newly created directory. Note that the directory will already have an ACL inherited from the parent directory. This code overrides that ACL. Frankly, I never rely on default ACLs inherited from a parent container you never know whether someone has set poor ACLs.

/* NT4ACL.cpp */ #include <windows.h> #include <stdio.h> #include <aclapi.h> PSID pEveryoneSID = NULL, pAdminSID = NULL, pNetworkSID = NULL; PACL pACL = NULL; PSECURITY_DESCRIPTOR pSD = NULL; //ACL will contain three ACEs: // Network (Deny Access) // Everyone (Read) // Admin (Full Control) try { const int NUM_ACES = 3; EXPLICIT_ACCESS ea[NUM_ACES]; ZeroMemory(&ea, NUM_ACES * sizeof(EXPLICIT_ACCESS)) ; //Create a well- known SID for the Network logon group. SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; if (!AllocateAndInitializeSid(&SIDAuthNT, 1, SECURITY_NETWORK_RID, 0, 0, 0, 0, 0, 0, 0, &pNetworkSID) ) throw GetLastError(); ea[0].grfAccessPermissions = GENERIC_ALL; ea[0].grfAccessMode = DENY_ACCESS; ea[0].grfInheritance= NO_INHERITANCE; ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; ea[0].Trustee.ptstrName = (LPTSTR) pNetworkSID; //Create a well-known SID for the Everyone group. SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY; if (!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryoneSID) ) throw GetLastError(); ea[1].grfAccessPermissions = GENERIC_READ; ea[1].grfAccessMode = SET_ACCESS; ea[1].grfInheritance= NO_INHERITANCE; ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; ea[1].Trustee.ptstrName = (LPTSTR) pEveryoneSID; //Create a SID for the BUILTIN\Administrators group. if (!AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminSID) ) throw GetLastError(); ea[2].grfAccessPermissions = GENERIC_ALL; ea[2].grfAccessMode = SET_ACCESS; ea[2].grfInheritance= NO_INHERITANCE; ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[2].Trustee.TrusteeType = TRUSTEE_IS_GROUP; ea[2].Trustee.ptstrName = (LPTSTR) pAdminSID; //Create a new ACL with the three ACEs. if (ERROR_SUCCESS != SetEntriesInAcl(NUM_ACES, ea, NULL, &pACL)) throw GetLastError(); //Initialize a security descriptor. pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); if (pSD == NULL) throw GetLastError(); if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) throw GetLastError(); // Add the ACL to the security descriptor. if (!SetSecurityDescriptorDacl(pSD, TRUE, // fDaclPresent flag pACL, FALSE)) { throw GetLastError(); } else { SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = FALSE; sa.lpSecurityDescriptor = pSD; if (!CreateDirectory("C:\\Program Files\\MyStuff", &sa)) throw GetLastError(); } // End try } catch(...) { // Error condition } if (pSD) LocalFree(pSD); if (pACL) LocalFree(pACL); // Call FreeSID for each SID allocated by AllocateAndInitializeSID. if (pEveryoneSID) FreeSid(pEveryoneSID); if (pNetworkSID) FreeSid(pNetworkSID); if (pAdminSID) FreeSid(pAdminSID);

This sample code is also available in the companion content in the folder Secureco2\Chapter06. As you can see, the code is not trivial, so let me explain what's going on. First you need to understand that you do not apply an ACL directly to an object you apply a security descriptor (SD). The SD is encapsulated in a SECURITY_ATTRIBUTES structure, which contains a field that determines whether the SD is inherited by the process. A security descriptor includes information that specifies the following components of an object's security:

  • An owner (represented by a SID), set using SetSecurityDescriptorOwner.

  • A primary group (represented by a SID), set using SetSecurityDescriptorGroup.

  • A DACL, set using SetSecurityDescriptorDacl.

  • An SACL, set using SetSecurityDescriptorSacl.

If any of the components of a security descriptor are missing, defaults are used. For example, the default owner is the same as the identity of the process calling the function or the Builtin Administrators group if the caller is a member of that group. In the preceding example, only the DACL is set. As mentioned, the security descriptor contains a DACL, and this is made up of one or more EXPLICIT_ACCESS structures. Each EXPLICIT_ACCESS structure represents one ACE. Finally, each EXPLICIT_ACCESS structure contains a SID and which permissions that SID has when attempting to use the object. The EXPLICIT_ACCESS structure also contains other details, such as whether the ACE is to be inherited. The process of creating an ACL is also illustrated in Figure 6-1.

Two other APIs exist for setting ACLs on files: SetFileSecurity and SetNamedSecurityInfo. SetFileSecurity is available in all versions of Windows NT, and SetNamedSecurityInfo is available in Windows NT 4 and later.

figure 6-1 the process of creating an acl.

Figure 6-1. The process of creating an ACL.

If your application runs on Windows 2000 or later, there is some relief from such code in the form of the Security Descriptor Definition Language, covered next.

Creating ACLs in Windows 2000

Recognizing that many people did not understand the ACL and security descriptor functions in Windows NT 4, the Windows 2000 security engineering team added a textual ACL and security descriptor representation called the Security Descriptor Definition Language (SDDL). Essentially, SIDs and ACEs are represented in SDDL through the use of well-defined letters.

More Info
Full details of the SDDL can be found in Sddl.h, available in the Microsoft Platform SDK.

The following example code creates a directory named c:\MyDir and sets the following ACE:

  • Guests (Deny Access)

  • SYSTEM (Full Control)

  • Administrators (Full Control)

  • Interactive Users (Read, Write, Execute)

/* SDDLACL.cpp */ #define _WIN32_WINNT 0x0500 #include <windows.h> #include <sddl.h> void main() { SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = FALSE; char *szSD = "D:P"  //DACL "(D;OICI;GA;;;BG)"  //Deny Guests  "(A;OICI;GA;;;SY)"  //Allow SYSTEM Full Control "(A;OICI;GA;;;BA)"  //Allow Admins Full Control  "(A;OICI;GRGWGX;;;IU)"; //Allow Interactive Users RWX if (ConvertStringSecurityDescriptorToSecurityDescriptor( szSD, SDDL_REVISION_1, &(sa.lpSecurityDescriptor), NULL)) { if (!CreateDirectory("C:\\MyDir", &sa )) { DWORD err = GetLastError(); } LocalFree(sa.lpSecurityDescriptor); } }

This code is significantly shorter and easier to understand than that in the Windows NT 4 example. Needing some explanation, however, is the SDDL string in the szSD string. The variable szSD contains an SDDL representation of the ACL. Table 6-2 outlines what the string means. You can also find this sample code in the companion content in the folder Secureco2\Chapter06.

Table 6-2. Analysis of an SDDL String

SDDL Component

Comments

D:P

This is a DACL. Another option is S: for audit ACE (SACL). The ACE follows this component. Note that the P option sets SE_DACL_PROTECTED, which gives you maximum control of the ACEs on the object by preventing ACEs from being propagated to the object by a parent container. If you don't care that ACEs are inherited from a parent, you can remove this option.

(D;OICI;GA;;;BG)

An ACE string. Each ACE is wrapped in parentheses.

D = deny ACE.

OICI = perform object and container inheritance. In other words, this ACE is set automatically on objects (such as files) and containers (such as directories) below this object or container.

GA = Generic All Access (Full Control).

BG = Guests group (also referred to as Builtin Guests).

This ACE prevents the guest account from accessing this directory or any file or subdirectory created beneath it.

The two missing values represent ObjectTypeGuid and Inherited ObjectTypeGuid, respectively. They are not used in this example because they apply only to object-specific ACEs. Object-specific ACEs allow you to have greater granularity control for the types of child objects that can inherit them.

(A;OICI;GA;;;SY)

A = allow ACE.

SY = SYSTEM (also called the local system account).

(A;OICI;GA;;;BA)

BA = Builtin Administrators group.

(A;OICI;GRGWGX;;;IU)

GR = Read, GW = Write, GX = Execute.

IU = Interactive users (users logged on at the computer).

Figure 6-2 shows the general layout of the SDDL string in the previous sample code.

figure 6-2 the sample sddl string explained.

Figure 6-2. The sample SDDL string explained.

No doubt you'll need to use other User accounts and Builtin accounts, so Table 6-3 presents a partial list of the well-known SIDs in Windows 2000 and later.

Table 6-3. SDDL SID Types

SDDL String

Account Name

AO

Account Operators

AU

Authenticated Users

BA

Builtin Administrators

BG

Builtin Guests

BO

Backup Operators

BU

Builtin Users

CA

Certificate Server Administrators

CO

Creator Owner

DA

Domain Administrators

DG

Domain Guests

DU

Domain Users

IU

Interactively Logged-On User

LA

Local Administrator

LG

Local Guest

NU

Network Logon User

PO

Printer Operators

PU

Power Users

RC

Restricted Code a restricted token, created using the CreateRestrictedToken function in Windows 2000 and later

SO

Server Operators

SU

Service Logon User any account that has logged on to start a service

SY

Local System

WD

World (Everyone)

NS

Network Service (Windows XP and later)

LS

Local Service (Windows XP and later)

AN

Anonymous Logon (Windows XP and later)

RD

Remote Desktop and Terminal Server users (Windows XP and later)

NO

Network Configuration Operators (Windows XP and later)

LU

Logging Users (Windows .NET Server and later)

MU

Monitoring Users (Windows .NET Server and later)

The advantage of SDDL is that it can be persisted into configuration files or XML files. For example, SDDL is used by the Security Configuration Editor .inf files to represent ACLs for the registry and NTFS.

More Info
During the Windows Security Push, access to the performance counters was tightened, which led to the creation of the Logging Users and Monitoring Users groups.

Creating ACLs with Active Template Library

The ATL is a set of template-based C++ classes included with Microsoft Visual Studio 6 and Visual Studio .NET. A new set of security-related ATL classes have been added to Visual Studio .NET to make managing common Windows security tasks, including ACLs and security descriptors, much easier. The following sample code, created using Visual Studio .NET, creates a directory and assigns an ACL to the directory. The ACL is

  • Blake (Read)

  • Administrators (Full Control)

  • Guests (Deny Access)

/* ATLACL.cpp */ #include <atlsecurity.h> #include <iostream> using namespace std; void main(){ try { //The user accounts CSid sidBlake("Northwindtraders\\blake"); CSid sidAdmin = Sids::Admins(); CSid sidGuests = Sids::Guests(); //Create the ACL, and populate with ACEs. //Note the deny ACE is placed before the allow ACEs. CDacl dacl; dacl.AddDeniedAce(sidGuests, GENERIC_ALL); dacl.AddAllowedAce(sidBlake, GENERIC_READ); dacl.AddAllowedAce(sidAdmin, GENERIC_ALL); //Create the security descriptor and attributes. CSecurityDesc sd; sd.SetDacl(dacl); CSecurityAttributes sa(sd); //Create the directory with the security attributes. if (CreateDirectory("c:\\MyTestDir", &sa)) cout << "Directory created!" << endl; } catch(CAtlException e) { cerr << "Error, application failed with error "  << hex << (HRESULT)e << endl; } }

NOTE
Note the use of Sids::Admins() and Sids::Guests() in the code. You should use these these values when dealing with well-known SIDs rather than the English names ( Administrators and Guests ) because the names might not be valid and the code will fail when running on non-English versions of Windows. You can view a list of all the well-known SIDs in the Sids C++ namespace in atlsecurity.h.

In my opinion, this code is much easier to understand than both the Windows NT 4 and Windows 2000 SDDL versions. It's easier than the Windows NT 4 code because it's less verbose, and it's easier than the Windows 2000 SDDL code because it's less cryptic. This sample code is also available in the companion content in the folder Secureco2\Chapter06.

Now that I've discussed how to define good ACLs for your application and methods for creating them, let's look at some common mistakes made when creating ACLs.



Writing Secure Code
Writing Secure Code, Second Edition
ISBN: 0735617228
EAN: 2147483647
Year: 2001
Pages: 286

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net