Detecting Windows Endpoint Compromise with SACLs

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.
  • 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.
  • 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
# 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:

Opening the document in Word generates a 4663 Event (File System Object Access)

Looking at the windows event log reveals that a 4663 (File System Object Access) event was logged. In this event, we can see the Object_Name references the KFC_Recipe.docx file, and the Process_Name references WINWORD.exe (Microsoft Word).

Let’s now try to access this file via another process:

Malicious file copying the KFC_Recipe.docx file.

In this instance, I used a malicious file (C:\Windows\Temp\runsystem32.exe) to simply copy the KFC_Recipe.docx file to C:\Windows\Temp prior to exfiltrating it.

In this instance, the SACL worked and our malicious binary was logged attempting to access our sensitive document. We can now operationalize this data by looking for anomalous processes accessing this file.

Alerting and Detection Strategies

Here are some examples of potential alerting and detection strategies that could be developed and exploited with appropriately configured SACLs:

Browser Data Harvesting

Alerting and Detection Strategy: Identify when a non-browser process accesses sensitive browser files, databases, and folders.

SACL Primitive: Object Read

Baseline Activity: Browser processes (e.g. Chrome and Firefox) will routinely access these files.

Activity Caught: Interrogation of browser history, theft of browser cookies, theft of browser login data, etc.

Powershell.exe accessing Chrome History DB

Key and Credential Harvesting

Alerting and Detection Strategy: Identify when a non-legitimate process accesses sensitive files, keys, and credential stores in a user’s home directory.

SACL Primitive: Object Read

Baseline Activity: Password vaults, SSH/SSH key manager binaries, GPG binaries, administration tools.

Activity Caught: Theft of SSH/GPG keys, theft of password vaults, theft of AWS/Azure credentials, etc.

Unusual process reading SSH keys.

Registry Persistence

Alerting and Detection Strategy: Identify malicious persistence — or malicious modification of legitimate persistence entries — in commonly-abused registry locations.

SACL Primitive: Object Write

Baseline Activity: Variable depending on software installation and behavior.

Activity Caught: Addition of new malicious binary run key entries, hijacking of malicious persistence entries, etc.

Legitimate persistence mechanism hijack by a malicious binary.

Malicious System Tampering

Alerting and Detection Strategy: Identify when there are malicious modifications to critical system security, logging, or protective controls.

SACL Primitive: Object Write

Baseline Activity: GPO activity, normal administrative changes.

Activity Caught: Modification of trust stores [PDF], modification of logging (e.g. commandline, powershell), manipulation of security tooling controls (e.g. sysmon configuration), etc.

Powershell logging disabled by a malicious binary.


There are a few protips that I’ve learned while deploying this across my home environment:

  • The objects you deploy a SACL to will determine the overall volume, but you should expect this technique to be very noisy. You likely will need to employ some tuning and whitelisting on the front-end to reduce the overall volume of indexed events.
  • You will need centralized log collection of Windows Event Logs to operationalize these detection strategies. You cannot rely on host-based logs stored only on the endpoint, so plan to ship off logs as quickly as possible. I recommend looking at the Windows Event Forwarding project from Palantir for a native way of collecting/managing logs for DFIR purposes.
  • There are tons of legitimate alerting strategies you can develop using SACLs. These are merely the tip of the iceberg, and I would encourage you to think deeply about what makes the most sense for your environment.
  • You can audit “Read Permissions”, “Change Permissions”, and “Take Ownership” to further monitor sensitive files. Attempts by an adversary to (a) enumerate and (b) manipulate audit ACEs applied to an object will be logged with a 4663 (filesystem) or 4657 (registry) event.
  • There is a nuclear auditing option called the Global Audit Policy. This can enable object-access auditing for all file-system objects, registry keys, or both. The benefit of this option is that it removes the need to manually handjam auditing ACEs onto the objects you care about. The downside is that the volume of audit events is astronomical. Global audit policy is outside the scope of this post.
  • Object access monitoring is one of many ways SACLs can be used. There are several other defensive primitives which are outside the scope of this post.

Further Reading and Acknowledgements

  • SpecterOps for their awesome work and research on ACE-based attacks and persistence.
  • Windows Internals Part 1 Seventh Edition