Securing NT Objects

Abstract

Windows provides a rich set of security features to secure its objects. Because of the vastness of the security features, its complicated documentation and contrastingly fewer examples, it’s still a poorly understood concept. This article is an attempt to describe the basic details of securing NT objects and the Win32 APIs required. Refer to the Microsoft developer network documentation for an exhaustive overview.

The article assumes that the reader has a basic idea of NT objects and the security subsystem. The accompanying source code is not complete by itself. It just describes the sequence of steps to secure an object. The source code needs to be modified as per the user’s requirement.

Securing an Object Using the NT Security Model

Security Descriptors

The security information associated with an object is defined by the SECURITY_DESCRIPTOR structure. This is a variable length structure. Four prime components of the structure that are worth attention are the Owner SID, the Group SID, the Discretionary ACL (DACL), and the System ACL (SACL).

The security descriptor should be initialized using the InitializeSecurityDescriptor API. If a NULL security is specified, the system assigns the processs default security descriptor to the object.


SECURITY_DESCRIPTOR SecDesc;

//Initilaize the security descriptor structure
if(!InitializeSecurityDescriptor(&SecDesc,
SECURITY_DESCRIPTOR_REVISION))
MyHandleErrorCondition(“Failed to initialize security
descriptor”);

After initializing the SD (SECURITY_DESCRIPTOR), the Owner ID, Group ID, DACL, and SACL need to be filled. The owner SID and the Group SID contain information about the primary owner and group for the particular object. If these parameters are NULL, the object is not owned by anyone. The owner and group SID can be set by the SetSecurityDescriptorOwner and SetSecurityDescriptorGroup APIs. The security identifier (SID) of the account(owner/group) that is to be set as the primary owner/group of the object needs to be passed as a parameter to these APIs. The SID for the account can be obtained by the LookupAccountName API.


/*
GetUserName API retrives the current user name whose SID is to be
retrieved. To obtain the SID for different user, pass the username
as a parameter to the pSidGetOwnerSid function which retrives SID
for a given account name
*/
if(!GetUserName(cCurrUser, &dwUserLen) ||
(NULL == (pSid = pSidGetOwnerSid(cCurrUser))))
MyHandleErrorCondition(“Failed to get owner SID”);

//Set the object owner in the security descriptor
if(!SetSecurityDescriptorOwner(&SecDesc, pSid, FALSE))
MyHandleErrorCondition(“Failed to set security descriptor owner”);

The user SID is obtained as follows


PSID
pSidGetOwnerSid(LPSTR lpUser)
{
DWORD dwSidLen = 0, dwDomainLen = 0;
SID_NAME_USE SidNameUse;

//The function on the first call retrieves the length that we
//need to initialize the SID & domain name pointers
if(!LookupAccountName(NULL, lpUser, NULL, &dwSidLen,
NULL, &dwDomainLen, &SidNameUse))
{
if(ERROR_INSUFFICIENT_BUFFER == GetLastError())
{
PSID pSid = LocalAlloc(LMEM_ZEROINIT, dwSidLen);
LPSTR lpDomainName = LocalAlloc(LMEM_ZEROINIT,
dwDomainLen);

if(pSid && lpDomainName &&
LookupAccountName(NULL, lpUser, pSid, &dwSidLen,
lpDomainName, &dwDomainLen, &SidNameUse))
return pSid;
}
}

printf(“\nFailed to get SID – %d”, GetLastError());

return NULL; //Was not able to retrive PSID
}

The ACLs & ACEs

After setting the primary owner and group of the object, the security descriptor’s access control list needs to be set. An Access Control List (ACL), as the name specifies, is a list of security information that manages access permissions to a securable object. ACLs contain a list of entries known as Access Control Entries (ACEs). Each ACE defines a set of access permissions for accounts in the system. An ACE can be defined as a container that contains the permissions for a particular trustee (group/user account) and the trustee’s identification in the form of security identifier (SID).

Note: Permission for all kinds of access to an object is implicitly denied by the system. You need to add an ACCESS_ALLOWED ACE explicitly to the ACL for the requested access to be granted.

The two types of ACLs are the Discretionary ACL (DACL) or the System ACL (SACL). Discretionary ACL (DACL) defines the access permissions to the particular object and the System ACL (SACL) defines the generation of audit message on attempts made over securable objects.

Discretionary ACL (DACL)

When a thread tries to access a securable object, the decision to grant the requested access is taken on the basis of the object’s DACL. A security descriptor can have a null DACL, an empty DACL, or DACL initialized with ACEs. When a securable object has no DACL (null DACL), no protection is assigned to the object. Hence, all kinds of access to the object is possible, thereby rendering the object insecure. Contrastingly, an empty DACL (an initialized DACL with no ACE) denies any access to the object. This is in keeping with the rule that you need to explicitly set permissions for all kinds of access to the object. Access permission of all kinds, by default, is denied implicitly. If a thread accesses a secured object, the system goes through the DACL list for the particular object comparing the access permission for each trustee of an ACE with the trustee list of the thread that accessed the object. The system examines the entire list of ACEs in sequence until one of the following events occurs.


  • An access-denied ACE denies any of the requested permissions to one of the trustees.

  • An access-allowed ACE allows all requested permissions explicitly to one of the trustees.

  • In case all ACEs were checked and still a requested permission(s) is pending for whom no explicit ACE was specified, the permission for the particular access is denied implicitly. The order of setting ACEs should follow the canonical order. In general, as specified in MSDN, the ACE should follow this hierarchy:


    • All explicit ACEs are placed in a group before any inherited ACEs.

    • Within the group of explicit ACEs, access-denied ACEs are placed before access-allowed ACEs.

    • Inherited ACEs are placed in the order in which they are inherited. ACEs inherited from the child object’s parent come first, and then ACEs inherited from the grandparent, and so on up the tree of objects.

    • For each level of inherited ACEs, access-denied ACEs are placed before access-allowed ACEs.

Any change in the order will not result in the desired security effect expected by the user. For example, consider Thread A with two trustees, FOO and BAR, accesses two different Objects, X and Y, with two ACEs each, as shown in the following picture.

Theoretically speaking, the rules mean the same for both objects: Deny access for trustee FOO and allow access for trustee BAR. But, when the thread accesses Object X, the system reads the first ACE and cycles it with the thread’s trustee list. Because FOO is denied access, the comparison stops there and the thread is denied access to the object. But, when the thread accesses Object Y, the system reads the first ACE of Object Y and cycles it through the thread’s trustee list. It finds that the trustee BAR is allowed access. If all the permissions requested are satisfied for this trustee, the cycling stops and the thread is granted access to Object Y.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read