Audit Generation
The Windows API provides functions enabling an administrator to monitor security-related events. The security descriptor for a securable object can have a SACL. A SACL contains ACEs that specify the types of access attempts that generate audit reports. Each ACE identifies a trustee, a set of access rights, and a set of flags that indicate whether the system generates audit messages for failed access attempts, successful access attempts, or both. The system writes audit messages to the security event log. To read or write an object's SACL, a thread must first enable the SE_SECURITY_NAME privilege. The Windows API also provides support for server applications to generate audit messages when a client tries to access a private object.
SACL Access Right
The ACCESS_SYSTEM_SECURITY access right controls the ability to get or set the SACL in an object's security descriptor. The system grants this access right only if the SE_SECURITY_NAME privilege is enabled in the access token of the requesting thread. To access an object's SACL:
To access a SACL using the GetNamedSecurityInfo() or SetNamedSecurityInfo() functions, enable the SE_SECURITY_NAME privilege. The function internally requests the access right. The ACCESS_SYSTEM_SECURITY access right is not valid in a DACL because DACLs do not control access to a SACL. However, you can use the ACCESS_SYSTEM_SECURITY access right in a SACL to audit attempts to use the access right.
Auditing Access To Private Objects
A protected server can use the following functions to generate audit reports in the security event log (Administrative Tools → Event Viewer).
Function |
Description |
AccessCheckAndAuditAlarm() |
Same as the AccessCheck() function except that it can generate audit messages for failed or successful access attempts. |
AccessCheckByTypeAndAuditAlarm() |
Same as the AccessCheckByType() function except that it can generate audit messages for failed or successful access attempts. |
AccessCheckByTypeResultListAndAuditAlarm() |
Same as the AccessCheckByTypeResultList() function except that it can generate audit messages for failed or successful access attempts. |
AccessCheckByTypeResultListAndAuditAlarmByHandle() |
Same as the AccessCheckByTypeResultListAndAuditAlarm() function except that it allows the calling thread to perform the access check before impersonating the client. |
ObjectCloseAuditAlarm() |
Generates an audit message to indicate that a client tried to close a private object. |
ObjectDeleteAuditAlarm() |
Generates an audit message to indicate that a client tried to delete a private object. |
ObjectOpenAuditAlarm() |
Generates an audit message to indicate that a client tried to open or create a private object. |
ObjectPrivilegeAuditAlarm() |
Generates an audit message to indicate that a client tried to use a specified set of privileges in conjunction with an attempt to access a private object. |
PrivilegedServiceAuditAlarm() |
Generates an audit message to indicate that a client tried to use a specified set of privileges. |
Table 16 |
Low-level Access Control
Low-level security functions help you work with security descriptors, ACLs, ACEs and other related access control components.
Low-level Security Descriptor Functions
There are several pairs of low-level functions for setting and retrieving an object's security descriptor. Each of these pairs works only with a limited set of Windows objects. For example, one pair works with file objects and another works with registry keys. The following table shows the low-level functions to use with the different types of securable objects.
Object type |
Low-level functions |
Files, Directories, Mailslots, Named pipes. |
Use the GetFileSecurity() and SetFileSecurity() functions. These functions use character strings to identify the securable object, instead of using handles. |
Processes, Threads, Access tokens, File-mapping objects, Semaphores, Events, Mutexes, Waitable timers. |
Use the GetKernelObjectSecurity() and SetKernelObjectSecurity() functions. |
Window stations, Desktops. |
Use the GetUserObjectSecurity() and SetUserObjectSecurity() functions. |
Registry keys. |
Use the RegGetKeySecurity() and RegSetKeySecurity() functions. |
Windows service objects. |
Use the QueryServiceObjectSecurity() and SetServiceObjectSecurity() functions. |
Printer objects. |
Use the PRINTER_INFO_2 structure with the GetPrinter() and SetPrinter() functions. |
Network shares. |
Use level 502 with the NetShareGetInfo() and NetShareSetInfo() functions. |
Private objects (objects private to the creating application). |
Use the CreatePrivateObjectSecurity(), DestroyPrivateObjectSecurity(), GetPrivateObjectSecurity() and SetPrivateObjectSecurity() functions. |
Table 17 |
Low-level Security Descriptor Creation
Low-level access control provides a set of functions for creating a security descriptor and getting and setting the components of a security descriptor. The low-level functions for initializing and setting the components of a security descriptor work only with absolute-format security descriptors. The low-level functions for getting the components of a security descriptor work with both absolute and self-relative security descriptors. The InitializeSecurityDescriptor() function initializes a SECURITY_DESCRIPTOR buffer. The initialized security descriptor is in absolute format and has no owner, primary group, DACL, or SACL. You can use the following low-level functions to get or set specific components of a specified security descriptor.
Function |
Description |
GetSecurityDescriptorControl() |
Retrieves revision and control information from a security descriptor. |
GetSecurityDescriptorDacl() |
Retrieves the DACL from a security descriptor. |
GetSecurityDescriptorGroup() |
Retrieves the primary group security identifier (SID) from a security descriptor. |
GetSecurityDescriptorLength() |
Returns the length of a security descriptor. |
GetSecurityDescriptorOwner() |
Retrieves the owner SID from a security descriptor. |
GetSecurityDescriptorSacl() |
Retrieves the SACL from a security descriptor. |
SetSecurityDescriptorDacl() |
Puts a DACL into a security descriptor, superseding any existing DACL. |
SetSecurityDescriptorGroup() |
Sets the primary group SID of a security descriptor. |
SetSecurityDescriptorOwner() |
Sets the owner SID of a security descriptor. |
SetSecurityDescriptorSacl() |
Puts a SACL into a security descriptor, superseding any existing SACL. |
Table 18 |
To check the revision level and structural integrity of a security descriptor, call the IsValidSecurityDescriptor() function.
Absolute and Self-Relative Security Descriptors
A security descriptor can be in either absolute or self-relative format. In absolute format, a security descriptor contains pointers to its information, not the information itself. In self-relative format, a security descriptor stores a SECURITY_DESCRIPTOR structure and associated security information in a contiguous block of memory. To determine whether a security descriptor is self-relative or absolute, call the GetSecurityDescriptorControl() function and check the SE_SELF_RELATIVE flag of the SECURITY_DESCRIPTOR_CONTROL parameter. You can use the MakeSelfRelativeSD() and MakeAbsoluteSD() functions for converting between these two formats.
The absolute format is useful when you are building a security descriptor and have pointers to all of the components, for example, when default settings for the owner, group, and discretionary ACL are available. In this case, you can call the InitializeSecurityDescriptor() function to initialize a SECURITY_DESCRIPTOR structure, and then call functions such as SetSecurityDescriptorDacl() to assign ACL and SID pointers to the security descriptor. In self-relative format, a security descriptor always begins with a SECURITY_DESCRIPTOR structure, but the other components of the security descriptor can follow the structure in any order. Instead of using memory addresses, the security descriptor's components are identified by offsets from the beginning of the descriptor. This format is useful when a security descriptor must be stored on disk, transmitted by means of a communications protocol, or copied in memory. Except for MakeAbsoluteSD(), all functions that return a security descriptor do so using the self-relative format. Security descriptors passed as arguments to a function can be either self-relative or absolute form.
Low-level ACL and ACE Functions
To create an ACL using the low-level functions, allocate a buffer for the ACL and then initialize it by calling the InitializeAcl() function. To add ACEs to the end of a DACL, use the AddAccessAllowedAce() and AddAccessDeniedAce() functions. The AddAuditAccessAce() function adds an ACE to the end of a SACL. You can use the AddAce() function to add one or more ACEs at a specified position in an ACL. The AddAce() function also allows you to add an inheritable ACE to an ACL. The DeleteAce() function removes an ACE from a specified position in an ACL. The GetAce() function retrieves an ACE from a specified position in an ACL. The FindFirstFreeAce() function retrieves a pointer to the first free byte in an ACL. To modify an existing ACL in an object's security descriptor, use the GetSecurityDescriptorDacl() or GetSecurityDescriptorSacl() function to get the existing ACL. You can use the GetAce() function to copy ACEs from the existing ACL. After allocating and initializing a new ACL, use functions such as AddAccessAllowedAce() and AddAce() to add ACEs to it. When you have finished building the new ACL, use the SetSecurityDescriptorDacl() or SetSecurityDescriptorSacl() function to add the new ACL to the object's security descriptor. You can use the AddAccessAllowedObjectAce(), AddAccessDeniedObjectAce(), or AddAuditAccessObjectAce() functions to add object-specific ACEs to the end of an ACL.