This post is going to focus on using the system access control list (SACL) functionality to detect endpoint compromise on Windows hosts. The goal is to quickly, cheaply, and effectively detect anomalous activity on an endpoint without focusing purely on anomalous process and thread execution.

If you’re unfamiliar with SACLs, the concept is quite simple: Apply an audit access control entry (ACE) to an object (e.g. a file, registry key) which logs when that object is successfully or unsuccessfully (or both) accessed or modified by one or more principles. These events will, with appropriate configuration, generate event log entries that may be analyzed to identify post-exploitation activity.

Note: This particular blog post will only cover object access and manipulation SACL entries. There are many other uses for SACLs which I may explore in future posts.

ACE and ACL Basics

Note: This is a very basic primer of ACEs, ACLs, and Security Descriptors. It is highly recommended you perform deeper research on these technologies to understand their nuance and internals.

Before we dig into deploying SACLs, we’ll cover a very basic and quick primer on access control lists (ACLs) and access control entries (ACE).

In the Windows world, an ACL is a list structure that can contain zero, one, or multiple ACE. Each ACE in an ACL describes a security identifier (SID) and specific access (or deny) rights allowed for that SID against a given object. For instance, an ACE can allow specific users to read/write/modify an object, while another ACE can deny access to the object altogether for other users.

ACLs are applied against securable objects, such as files, folders, registry keys, and kernel objects. While there are many documented securable objects, research has identified there are still many dozens of objects that remain undocumented.

An ACL can be one of two specific varieties: a discretionary access control list (DACL) or a system access control list (SACL). The DACL is primarily used for controlling access to an object, whereas a SACL is primarily used for logging access attempts to an object.

There’s a lot going on here, so we’ll look at a quick visual:

We’ll first walk through the DACL. As noted in the above image, the DACL has both Access Denied and Access Allowed ACEs. When an attempt is made to access a securable objects, the system will check the ACEs specified in the object’s DACL and make a determination on whether access should be allowed or not.

This is referenced in the following diagram:

In this example, a process is attempting to access a securable object and the token is inspected and compared against ACE entries on the object’s DACL. As the SIDs specified in the access token match those specified in the ACE, access is granted.

This diagram also outlines the order of ACE evaluation when attempting to access an object:

Explicit Deny ACEs

Explicit Allow ACEs

Inherited Deny ACEs

Inherited Allow ACEs

We’ll next walk through the SACL, the often-ignored and underutilized friend of the DACL. Unlike the DACL, the SACL provides access to the Audit ACE. The audit ACE simply describes whether or not access to an object was allowed, denied, or both, and with what access was granted.

This will prove to be incredibly valuable for a few key reasons:

DACLs generally won’t stop post-exploitation activity for a given user. Compromise of a user’s process will allow the adversary to access the user’s files and folders with impunity.

Endpoint detection and response tooling has major blind spots. Some tooling may straight up ignore file reads while others may only report changes to objects, but not specify what changed.

We can quickly apply audit ACEs to objects and record access (successful or not) for specific actions (e.g. read, write, delete, permission changes, etc.) without requiring additional tooling or telemetry sources.

In summary: DACLs control what SIDs can access what objects, while SACLs tell you whether or not they were successful in their attempt to perform a given set of actions against that object.

Defensive SACL Primitives

There two defensive SACL primitives that we will use in this post:

Object Read SACLs . These are SACLs designed to detect unusual post-exploitation activity focused on credential theft, privilege escalation, file pilfering, etc. The premise is simple: Log when someone accesses this object.

. These are SACLs designed to detect unusual post-exploitation activity focused on credential theft, privilege escalation, file pilfering, etc. The premise is simple: Log when someone accesses this object. Object Write SACLs. These are SACLs designed to detect writes, modifications, permission changes, or other activity used for persistence, anti-forensics, or system tampering. The premise is simple: Log when someone writes to/modifies this object.

These two object primitives can be easily applied to both file system and registry objects. These primitives are defined in powershell as follows:

# SACL Primitive for File Reads / Directory Traversals / Ownership Changes $AuditUser = "Everyone" $AuditRules = "ReadData, TakeOwnership" $InheritType = "None" $PropagationFlags = "None" $AuditType = "Success" $FileReadSuccessAudit = New-Object System.Security.AccessControl.FileSystemAuditRule($AuditUser,$AuditRules,$InheritType,$PropagationFlags,$AuditType) # SACL Primitive for File Writes / Appends / Deletes / Ownership Changes $AuditUser = "Everyone" $AuditRules = "CreateFiles, AppendData, DeleteSubdirectoriesAndFiles, Delete, TakeOwnership" $InheritType = "None" $PropagationFlags = "None" $AuditType = "Success" $FileWriteSuccessAudit = New-Object System.Security.AccessControl.FileSystemAuditRule($AuditUser,$AuditRules,$InheritType,$PropagationFlags,$AuditType) # SACL Primitive for Registry Key Value Sets / Key Creation / Key Writes / Ownership Changes $AuditUser = "Everyone" $AuditRules = "SetValue, CreateSubkey, WriteKey, TakeOwnership" $InheritType = "None" $PropagationFlags = "None" $AuditType = "Success" $RegistryWriteSuccessAudit = New-Object System.Security.AccessControl.RegistryAuditRule($AuditUser,$AuditRules,$InheritType,$PropagationFlags,$AuditType) # SACL Primitive for Registry Key Value Sets / Key Creation / Key Writes / Ownership Changes $AuditUser = "Everyone" $AuditRules = "SetValue, CreateSubkey, WriteKey, TakeOwnership" $InheritType = "ContainerInherit, ObjectInherit" $PropagationFlags = "None" $AuditType = "Success" $RegistryRecursiveWriteSuccessAudit = New-Object System.Security.AccessControl.RegistryAuditRule($AuditUser,$AuditRules,$InheritType,$PropagationFlags,$AuditType)

Adding a SACL

Note: You will need appropriate Windows event logging enabled for object manipulation event IDs to log. I recommend the audit settings specified here.

Now, we’ll walk through how to configure a SACL manually. This is ill-advised for any production deployment, but it’s important to know how to investigate and modify these by hand.

For this example, we’ll configure a SACL on a super secret filed called KFC_Recipe.docx with the object read primitive we described above. The goal will be to log any access to this file and then identify unusual access by investigating the events.

Using the Security GUI

Navigate to the KFC_Recipe.docx file and Right-click > Properties .

file and . Click on the Security tab.

Click Advanced.

Click on the Auditing tab.

Our unprotected KFC Recipe. Shame.

This panel will detail any configured audit ACEs configured on the object. As we haven’t added an ACE, it will be empty. Let’s fix that.

Click Add.

We first need to specify a principal (SID) to audit.

Click on “ Select a Principal ”.

”. Type “Everyone” and Check Names. Hit OK.

This will audit all principals that access this object.

Click the Advanced button and uncheck all boxes except List Folder / Read Data. Set the Type to All.

This will audit all principals that successfully or unsuccessfully read the file.

The final output of the audit ACE.

Using Powershell

This process is substantially easier to deploy via powershell:

# SACL Primitive for File Reads / Directory Traversals / Ownership Changes $AuditUser = "Everyone" $AuditRules = "ReadData, TakeOwnership" $InheritType = "None" $PropagationFlags = "None" $AuditType = "Success" $FileReadSuccessAudit = New-Object System.Security.AccessControl.FileSystemAuditRule($AuditUser,$AuditRules,$InheritType,$PropagationFlags,$AuditType) $FilePath = "$ENV:USERPROFILE\Documents\KFC_Recipe.docx" # Get the ACL with Audit ACEs

$Acl = Get-Acl $FilePath -Audit # Set the ACE

$Acl.SetAuditRule($FileReadSuccessAudit) # Apply the ACL

$Acl | Set-Acl | Out-Null

Verify the ACE Works

Now we’ll perform validation on this ACE to ensure it’s logging appropriately.

We’ll first test as an innocuous user by opening the document in Microsoft Word: