A Hitch-hacker's Guide to DACL-Based Detections (Part 1B)
This blog series was co-authored by Security Consultant Megan Nilsen and TAC Practice Lead Andrew Schwartz.
1 Introduction
In this continuation to our first post (Part 1A), we will further explore Active Directory (AD) attribute-based attacks and their subsequent detections. This post will complete the stepping through of the flow chart provided in the DACL section of the Hacker Recipes.
Although this post will make use of a variety of different “attack” tools, it should be noted that the tool is a means for use to execute the attack, but we are more focused on the underlying techniques of modifiable attributes and the detections surrounding them.
Just as the first post established, a couple of reminders:
- We are operating under the assumption that the adversary already has a foothold within the domain and has acquired the appropriate access they need to make modifications to the objects we will discuss.
- Post-exploitation is not a focus.
- Intelligence applied to adversary attribution has not been mapped.
- A subset of Windows Event logging has been used, and not all the possible telemetry data points within this data set have been analyzed.
2 Logging Setup
As noted in Part 1A, for telemetry purposes, we will be relying on setting an “Auditing” system access control list (SACL) on each of these attributes and the following Windows Event IDs:
Configuring a SACL is an additional step that must be taken even if the above listed Windows Events are currently being ingested.
Please refer to Part 1A on how to enable and configure the logging setup of the SACL and how to enable/ingest the above Windows Event IDs.
3 Blog Format
Due to the length of this post and the number of attributes covered, it is important to remember a couple of key formatting guidelines from Part 1A as we step through this post.
Each section will contain the following headings:
- Name of the Attribute (common name (CN) of the attribute)
- Background
- Will cover a brief overview of what the attribute (LDAP-Display-Name) is and the relevant links to Microsoft documentation
- Modifying the Attribute (Attack)
- Will cover how the “attack” was performed, including relevant setup for modifying the attribute in question, screenshots/commands, and tools used
- If additional auditing was enabled for building the detection, it will also likely be covered here—or, if additional setup was more complex, it will be broken out into a preceding or subsequent heading.
- Building the Detections
- Will cover a variety of detections that will include a range of complexity
- As was stated in the introduction, not all the possible telemetry data points within this data set have been analyzed. However, we have tried our best to cover the Event IDs that are most accessible and prominent for building out detections.
- Where necessary, we will provide a flow of logic for detections that involve more complexity or additional information to interpret what is being shown. However, most detections will follow a similar format and will not be explained in further detail.
4 Object Modifications & Detections
4.1 AddMember
4.1.1 Background
In this case, AddMember refers to the PowerView cmdlet or Linux RPC commands that can be used to perform the attack, and not necessarily the attribute/object misconfiguration that we are actually leveraging.
More specifically, AddMember is referring to the collective permissions granted to a security principal based on the security descriptor definition language (SDDL)/ACE defined for the group object based on the following AD permissions:
- GenericAll
- GenericWrite
- Self
- AllExtendedRights—Note: When we experimented with modifying group membership by allowing for AllExtendedRights without adding additional permissions, we were unable to modify the group membership.
OR
4.1.2 Modifying the Objects (Attack) - Generic Write
Once again, this blog post assumes that an attacker has discovered domain objects that are already misconfigured to allow for exploitation. However, in this case, we will need to perform the misconfiguration to our Domain Admins group object discretionary access control list (DACL) prior to running our “attack.”
As done previously, we first created a new standard user account to easily track changes within our SIEM.
Figure 45 - New Imposter Account
Then, using our Domain Admin account head.chef, we modified the Domain Admins group directly through Active Directory Users and Computers (ADUC), giving the imposter.oatmeal account the ability to abuse the misconfigured DACL.
Figure 46 - Modifying User to Have Generic Write
And finally, to validate that we were able to abuse the misconfiguration to the Domain Admins DACL, we ran the attack as specified in the Hacker Recipes for AddMember. As you can see, the imposter.oatmeal was able to successfully add itself to the Domain Admins group.
Figure 47 - Adding imposter.oatmeal to Domain Admins
Figure 48 - Confirmation of Group Add
4.1.3 Building the Detections
4.1.3.1 Detection With Event IDs 5136 and 4662
Please note that for the following detection, you will need to adjust the following fields to account for the GUIDS/normalized names of the Groups you are looking to monitor:
- GUID
- Object_Name
As an additional note, while the attack above only demonstrates adding “Generic Write” for our user object, this detection will also catch changes made to the other attributes listed above.
Finally, this detection is specifically identifying when changes to the Domain Admins group DACL are made, and not when the account is being added to the Domain Admins group.
((index=main EventCode=5136 Class=group LDAP_Display_Name=nTSecurityDescriptor GUID="{5b2c1c16-8aab-47ef-a55a-7a94684a65c4}") OR (index=main Account_Name!=*$ Object_Type="%{bf967a9c-0de6-11d0-a285-00aa003049e2}" Object Name="%{5b2c1c16-8aab-47ef-a55a-7a94684a65c4}" EventCode=4662 Access_Mask = 0x40000))
| eval Logon_ID=if(EventCode==4662,mvindex(Logon_ID,-1), mvindex(Logon_ID,-1))
| eval user=if(EventCode==4662,mvindex( Account_Name,-1), mvindex( Account_Name,-1))
| eval DACL=if(EventCode==5136,mvindex( Value,-1), mvindex( Value,-1))
| join type=outer Logon_ID
[ search index=main Account_Name!=*$ Object_Type="%{bf967a9c-0de6-11d0-a285-00aa003049e2}" Object_Name="%{5b2c1c16-8aab-47ef-a55a-7a94684a65c4}" EventCode=4662 Access_Mask = 0x40000
| eval Props=Properties
| eval AccessMask=Access_Mask
| eval ObjectType=Object_Type
| eval ObjectName=Object_Name
|table Account_Name,Logon_ID,Props,AccessMask,ObjectType, ObjectName]
| table time, LogonID, Account_Name, Props, AccessMask, ObjectType, ObjectName, DN, GUID, DACL, Class, Type
|stats values by time, LogonID, DACL
Figure 49 - AddMember Final Query (1)
Figure 50 - AddMember Final Query (2)
If we review the SDDL using SDDL-Converter, we can see the rights added for imposter.oatmeal to the Domain Admins DACL.
Figure 51 - SDDL Review
To detect the imposter.oatmeal account being added to the Domain Admins group, we will use the Member attribute as the LDAP_Display_Name.
4.1.3.2 Detection With Event IDs 5136, 4662, and 4624
index=main ((EventCode=5136 AND LDAP_Display_Name=member) OR (EventCode=4624 AND Account_Name!="*$" AND Account_Name!="ANONYMOUS LOGON" AND Account_Name!="SYSTEM") OR (EventCode=4662 AND Access_Mask=0x20))
| eval Logon_ID=if(EventCode==4624,mvindex(Logon_ID,-1), mvindex(Logon_ID,-1))
| eval Mod_Account=if(EventCode==4624,mvindex(Account_Name,-1), mvindex(Account_Name,-1))
| eval Changed_Value=if(EventCode==5136,mvindex(Value,-1), mvindex(Value,-1))
| join type=outer Logon_ID
[ search (EventCode=5136) OR (EventCode=4624)
| stats count by Logon_ID, Account_Name, Source_Network_Address
| table Account_Name,Logon_ID, Source_Network_Address ]
| join type=outer Logon_ID
[ search index=main Account_Name!=*$ EventCode=4662 Access_Mask = 0x20
| eval Props=Properties
| eval AccessMask=Access_Mask
| eval ObjectType=Object_Type
| eval ObjectName=Object_Name
| rex field=Message "(?<Object_Properties>(?ms)(?<=)Properties:(.*?)(?=Additional\s+))"
|table Account_Name,Logon_ID,Props,AccessMask,ObjectType, ObjectName, Object_Properties]
| table time, ModAccount, Source_Network_Address , Class, DN, Logon_ID, Type, LDAP_Display_Name, Changed_Value, AccessMask, Props, Object_Properties
| where len(Class)>0
| stats values by time, ChangedValue
Figure 52 - Detecting Addition of Account to Domain Admins Group (1)
Figure 53 - Detecting Addition of Account to Domain Admins Group (2)
4.2 ForceChangePassword
4.2.1 Background
ForceChangePassword is the section referenced within the Hacker Recipes that defines a collection of permissions that allow for control over changing the password of another user/object. More specifically, the permissions referenced are:
- GenericAll
- AllExtendedRights
- User-Force-Change-Password
For this particular section, we will focus on the “User-Force-Change-Password” right. However, the detections built should detect the use of all three (3) rights.
4.2.2 Modifying the Objects
Note: For this section, you will need to enable auditing on the specific user/computer object you are looking to monitor.
Below, you can see that in addition to the default privileges that are generated when you create a regular user account in AD, I have added imposter.oatmeal as a grantee on an ACE, checked “Reset Password,” and then applied those changes to the user.
Figure 54 - Adding Privileges to Account
Now we can utilize PowerSploit to change the password of our victim account.
Figure 55 - Changing Account Password with PowerSploit
Figure 56 - Validating Password Change With CrackMapExec
4.2.3 Building the Detections
4.2.3.1 Detection With Event IDs 5136 and 4662
This detection is nearly identical to the one that was built for the AddMember section; the only modifications you will need to make are to Object_Type, which reflects the “User” object within your AD domain. Remember that this is a lab environment and not an organizational production environment; you may wish to use the Object_Name filter to either exclude or to build a list of users to monitor if logging volume is high for this particular detection.
As was done with the AddMember section previously, the following detection focuses on adding the “ForceChangePassword” right to the user object’s egg.bacon’s DACL.
((index=main EventCode=5136 Class=user LDAP_Display_Name=nTSecurityDescriptor) OR (index=main Account_Name!=*$ Object_Type="%{bf967aba-0de6-11d0-a285-00aa003049e2}" EventCode=4662 Access_Mask = 0x40000))
| eval Logon_ID=if(EventCode==4662,mvindex(Logon_ID,-1), mvindex(Logon_ID,-1))
| eval user=if(EventCode==4662,mvindex( Account_Name,-1), mvindex( Account_Name,-1))
| eval DACL=if(EventCode==5136,mvindex( Value,-1), mvindex( Value,-1))
| join type=outer Logon_ID
[ search index=main Account_Name!=*$ Object_Type="%bf967aba-0de6-11d0-a285-00aa003049e2}}" EventCode=4662 Access_Mask = 0x40000
| eval Props=Properties
| eval AccessMask=Access_Mask
| eval ObjectType=Object_Type
| eval ObjectName=Object_Name
|table Account_Name,Logon_ID,Props,AccessMask,ObjectType, ObjectName]
| table time, LogonID, Account_Name, Props, AccessMask, ObjectType, ObjectName, DN, GUID, DACL, Class, Type
|stats values by time, LogonID,DACL
Figure 57 - Final Query Detecting ForceChangePassword Modification (1)
Figure 58 - Final Query Detecting ForceChangePassword Modification (2)
4.3 GrantOwnerShip
4.3.1 Background
GrantOwnerShip (or Generic Write on User) is the section referenced within the Hacker Recipes that defines a collection of permissions that allows for more general control of another user/object. More specifically, the permissions referenced are:
- GenericAll
- WriteOwner
For this section, we will be focusing on the WriteOwner permission. However, the detections built should detect the use of both rights.
4.3.2 Modifying the Objects (Attack)
First, we begin by “misconfiguring” our dacled.egg object to give imposter.oatmeal, our attacker account, WriteOwner permissions.
Figure 59 - Modifying User Account for WriteOwner
Then we use PowerView to set ownership.
Figure 60 - Setting Ownership with PowerView
4.3.3 Building the Detections
The same detection we built to detect the modifications to user objects for ForceChangePasswordwill also pick up changes made to the user object for WriteOwner.
Figure 61 - Detecting Misconfiguration of User Object (1)
Figure 62 - Detecting Misconfiguration of User Object (2)
Figure 63 - SDDL Detecting WriteOwner Privileges on dacled.egg
It will also identify when the owner of the object has been changed with the PowerShell cmdlet.
Figure 64 -SDDL Post Ownership Change
4.4 LAPS and gMSA
The following sections will involve two (2) attributes specific to both gMSA and LAPS. For background context and detections, please refer to Andrew’s post on gMSA and Megan’s post on Local Administrator Password Solution (LAPS).
4.4.1 ms-Mcs-AdmPwdExpirationTime (LAPS):
4.4.2 Background
The ms-Mcs-AdmPwdExpirationTime attribute determines when a LAPS password is due to expire and when it will, by proxy, refresh the local administrative password for the machine.
4.4.3 Modifying the Attribute
Before we can modify the attribute, we must first convert the time of the new password expiration that’s stored inside the given AD attribute in Windows File time format.
The easiest way to do this is to simply use the PowerShell AD module to first query the computer and object whose expiration time you’re trying to identify, and then use PowerShell to convert the time stamp to human-readable format.
Get-ADComputer PAN-PC -Properties ms-Mcs-AdmPwdExpirationTime
[datetime]::FromFileTimeUTC(133308023294324030)
Figure 65 - Reading ms-MCS-AdmPwdExpirationTime Attribute
When changing the expiration time using PowerMad, your specified value will need to be submitted in the Windows File time format.
Figure 66 - Modifying Attribute with PowerMad
4.4.4 Building the Detections
4.4.4.1 Detection With Event IDs 5136, 4662, and 4624
index=main ((EventCode=5136 AND LDAP_Display_Name=ms-Mcs-AdmPwdExpirationTime) OR (EventCode=4624 AND Account_Name!="*$" AND Account_Name!="ANONYMOUS LOGON" AND Account_Name!="SYSTEM") OR (EventCode=4662 AND Access_Mask=0x20))
| eval Logon_ID=if(EventCode==4624,mvindex(Logon_ID,-1), mvindex(Logon_ID,-1))
| eval Mod_Account=if(EventCode==4624,mvindex(Account_Name,-1), mvindex(Account_Name,-1))
| join type=outer Logon_ID
[ search (EventCode=5136) OR (EventCode=4624)
| stats count by Logon_ID, Account_Name, Source_Network_Address
| table Account_Name,Logon_ID, Source_Network_Address ]
| join type=outer Logon_ID
[ search index=main Account_Name!=*$ EventCode=4662 Access_Mask = 0x20
| eval Props=Properties
| eval AccessMask=Access_Mask
| eval ObjectType=Object_Type
| eval ObjectName=Object_Name
| rex field=Message "(?<Object_Properties>(?ms)(?<=)Properties:(.*?)(?=Additional\s+))"
|table Account_Name,Logon_ID,Props,AccessMask,ObjectType, ObjectName, Object_Properties]
| table time, ModAccount, Source_Network_Address , Class, DN, Logon_ID, Type, LDAP_Display_Name, Value, AccessMask, Props, Object_Properties
| where len(Class)>0
| stats values by time, Value, LogonID
Figure 67 - Detection With Event IDs 5136, 4624, 4662 (1)
Figure 68 - Detection With Event IDs 5136, 4624, 4662 (2)
4.4.4.2 Detection With Event IDs 5136, 4662, and 4624, Excluding Machine Accounts
It is important to note that the previous detection will also catch legitimate changes to LAPS passwords. To filter out the legitimate events, we can remove the machine accounts.
That said, it is advisable to keep both queries to ensure that detection is not evaded if an attacker creates or compromises a machine account.
index=main ((EventCode=5136 AND LDAP_Display_Name=ms-Mcs-AdmPwdExpirationTime) OR (EventCode=4624 AND Account_Name!="*$" AND Account_Name!="ANONYMOUS LOGON" AND Account_Name!="SYSTEM") OR (EventCode=4662 AND Access_Mask=0x20))
| eval Logon_ID=if(EventCode==4624,mvindex(Logon_ID,-1), mvindex(Logon_ID,-1))
| eval Mod_Account=if(EventCode==4624,mvindex(Account_Name,-1), mvindex(Account_Name,-1))
| join type=outer Logon_ID
[ search (EventCode=5136) OR (EventCode=4624)
| stats count by Logon_ID, Account_Name, Source_Network_Address
| table Account_Name,Logon_ID, Source_Network_Address ]
| join type=outer Logon_ID
[ search index=main Account_Name!=*$ EventCode=4662 Access_Mask = 0x20
| eval Props=Properties
| eval AccessMask=Access_Mask
| eval ObjectType=Object_Type
| eval ObjectName=Object_Name
| rex field=Message "(?<Object_Properties>(?ms)(?<=)Properties:(.*?)(?=Additional\s+))"
|table Account_Name,Logon_ID,Props,AccessMask,ObjectType, ObjectName, Object_Properties]
| search Mod_Account!=*$
| table time, ModAccount, Source_Network_Address , Class, DN, Logon_ID, Type, LDAP_Display_Name, Value, AccessMask, Props, Object_Properties
| where len(Class)>0
| stats values by time, Value, LogonID
Figure 69 - Detection Excluding Machine Accounts (1)
Figure 70 - Detection Excluding Machine Accounts (2)
4.5 PrincipalsAllowedToRetrieveManagedPassword/ms-DS-GroupMSAMembership (gMSA)
4.5.1 Background
The PrincipalsAllowedToRetrieveManagedPassword AD attribute governs who can read the gMSA Password.
However, it is important to outline that the “PrincipalsAllowedToRetrieveManagedPassword” value isn’t the attribute that we should be auditing, since this value is only used when making changes within PowerShell.
Instead, the change made with the "PrincipalsAllowedToRetrieveManagedPassword" flag stores the value assigned in the PowerShell cmdlet to the ms-DS-GroupMSAMembership attribute within AD. This interpretation is also backed up by this article.
Figure 71 - Pre- and Post-Change Values of msDS-GroupMSAMEmbership attribute
An attacker who can insert themselves into this attribute then has the ability to read the gMSA account password itself.
4.5.2 Modifying the Objects (Attack)
Using the Active Directory PowerShell module, we first will use the “PrincipalsAllowedToRetrieveManagedPassword” flag to give the created gMSA account granola.bar permissions over the Domain Admins group.
Figure 72 - Modifying the gMSA Account
Get-ADServiceAccount -Identity granola.bar -Properties *
Figure 73 - Validating Changes to gMSA Account
4.5.3 Building the Detections
4.5.3.1 Detection with Event IDs 5136, 4662, and 4624
index=main ((EventCode=5136 AND LDAP_Display_Name=msDS-GroupMSAMembership) OR (EventCode=4624 AND Account_Name!="*$" AND Account_Name!="ANONYMOUS LOGON" AND Account_Name!="SYSTEM" AND Source_Network_Address!="-") OR (EventCode=4662 AND Access_Mask=0x20))
| eval Mod_Account=if(EventCode==4624,mvindex(Account_Name,-1), mvindex(Account_Name,-1))
| eval LogonID=mvindex(Logon_ID,0)
| eval LogonID_2=mvindex(Logon_ID,1)
| join type=outer Logon_ID
[ search (EventCode=5136 AND LDAP_Display_Name=msDS-GroupMSAMembership)
| table Logon_ID, ]
| join type=outer LogonID_2
[ search (EventCode=4624 AND Account_Name!="*$" AND Account_Name!="ANONYMOUS LOGON" AND Account_Name!="SYSTEM" AND Source_Network_Address!="-")
| table Account_Name,LogonID_2, Source_Network_Address ]
| join type=outer Logon_ID
[ search index=main Account_Name!=*$ EventCode=4662 Access_Mask = 0x20
| eval Props=Properties
| eval AccessMask=Access_Mask
| eval ObjectType=Object_Type
| eval ObjectName=Object_Name
| rex field=Message "(?<Object_Properties>(?ms)(?<=)Properties:(.*?)(?=Additional\s+))"
|table Account_Name,Logon_ID,Props,AccessMask,ObjectType, ObjectName, Object_Properties]
| search Mod_Account!=*$
| table time, LogonID, Mod_Account, Source_Network_Address , Class, DN, Type, LDAP_Display_Name, Value, AccessMask, Props, Object_Properties
| where len(Class)>0
| stats values by time, LogonID, DN
Figure 74 - Detection With Event IDs 4662, 5136 and 4624 (1)
Figure 75 - Detection With Event IDs 4662, 5136 and 4624 (2)
4.6 DCSync Rights
4.6.1 Background
The ability to perform a DCSync is prized by attackers everywhere—unsurprising, considering that having the ability to extract a domain ntds.dit file is, in many ways, how you can obtain the proverbial keys to the AD kingdom.
However, while there are many detections written to detect a DCSync happening, there are far fewer detections designed to identify when the objects that make up the privileges to do a DCSync are modified to grant an unapproved user access to perform a DCSync. The tool that I leveraged to add DCSync rights added the three (3) following rights:
- DS-Replication-Get-Changes
- DS-Replication-Get-Changes-All
- DS-Replication-Get-Changes-In-Filtered-Set
However, it is important to note that DS-Replication-Get-Changes-In-Filtered-Set is not specifically needed for an account to perform a DCSync. Only the first two (2) bullets are needed.
An important note shared with us from Jim Sykora (@jimsycurity) is that AllExtendedRights include all DCSync rights. Additionally, GenericAll includes AllExtendedRights. So, if you are looking for just the three (3) DCSync rights listed above, you may miss the broader permission sets.
4.6.2 Creating a New Account & Modifying the Objects
To start, we create a new user called imposter.egg that will be the account we will use to modify DACL rights to perform the DCSync.
net user imposter.egg /domain
Figure 76 - Creating an "Imposter" Account
We can use PowerView to add an ACE to domain root to grant DCSync permissions to the attacker user account. A change is made on the domainDNS root DACL via an ACE that corresponds to the user’s SID.
Add-ObjectACL -PrincipalIdentity imposter.egg -Rights DCSync
Figure 77 - Adding DCSync Rights
If we look at our privileges now, we can see the new rights reflected:
Figure 78 - Account SID
Figure 79 - Newly Added ACEs Added to Domain Root DACL
Now that our new user has been given the right access, we can perform DCSync using Impacket’s secretsdump.py.
Figure 80 - DCSync
4.6.3 Building the Detections
A couple of quick notes here—the imposter.egg security principal is the grantee of the ACE added to the domain root DACL. When building a detection to identify DCSync rights added to a user or computer account, we will not be building a detection that searches for DS-Replication-Get-Changesor DS-Replication-Get-Changes-All. Instead, we will be building detections that utilize the domainDNS class, as changes for DCSync rights are made to the domain root, and not the attributes that we are adding.
Note: If you modify the DCSync objects directly, you will generate an Event ID 5136 event with the objects of the same name. However, when I attempted this (adding an imposter.egg account to each object), I was unable to perform a successful DCSync. Jim helped clarify that the reason behind this is because for DCSync (and several other rights), it only matters if the Security Principal is granted/delegated those rights at the domain root object.
Instead, this time, we will be hunting for an Event 5136, with domainDNS as the class and the actual domain as the “DN.”
4.6.3.1 Detection with Event ID 5136
index=main EventCode=5136 Class=domainDNS DN="DC=BREAKFASTLAND,DC=LOCAL" | table time, LogonID, DN, GUID, Value, Class, Type
Figure 81 - Basic DCSync Query (1)
Figure 82 - Basic DCSync Query (2)
This is a decent query for detecting adding privileges for the broader domain and works both with privilege modifications through Powerview and when privileges are modified manually in ADUC. However, in our opinion, this alone lacks a lot of telemetry. Adding a correlation with Event ID 4662 for additional context is a good idea.
The “object type” in this case is the GUID of the domainDNS object, and “object_name” is the GUID of our domain (BREAKFASTLAND.LOCAL), which was used in our detection based on 5136 and can be filtered to look for “WRITE_DAC” access via an access mask of 0x4000.
4.6.3.2 Detection with Event ID 4662
index=main Account_Name!=*$ Object_Type="%{19195a5b-6da0-11d0-afd3-00c04fd930c9}" Object_Name="%{754fb287-55d2-4d68-b7fc-0332e1746740}" EventCode=4662
Access_Mask = 0x40000 | table time, Logon_ID, Account_Name, Access_Mask, Object_Type, Object_Name, Properties
Figure 83 - DCSync With Event ID 4662
Then, if you are using a SIEM with the ability to utilize a complex query language, you can combine the two (2) queries to put all the telemetry in one (1) view. As a quick note, this does place all the entries in the “Value” field in one (1) column, but it condenses the table view in Splunk to one (1) line, making it easy to quickly look at and reference the events in a separate tab if more detail is needed.
4.6.3.3 Detection with Event ID 5136 and 4662
index=main ((EventCode=5136 Class=domainDNS DN="DC=BREAKFASTLAND,DC=LOCAL") OR (index=main Account_Name!=*$ Object_Type="%{19195a5b-6da0-11d0-afd3-00c04fd930c9}" Object_Name="%{754fb287-55d2-4d68-b7fc-0332e1746740}" EventCode=4662 Access_Mask = 0x40000))
| eval Logon_ID=if(EventCode==4662,mvindex(Logon_ID,-1), mvindex(Logon_ID,-1))
| eval user=if(EventCode==4662,mvindex( Account_Name,-1), mvindex( Account_Name,-1))
| join type=outer Logon_ID
[ search index=main Account_Name!=*$ Object_Type="%{19195a5b-6da0-11d0-afd3-00c04fd930c9}" Object_Name="%{754fb287-55d2-4d68-b7fc-0332e1746740}" EventCode=4662 Access_Mask = 0x40000
| eval Props=Properties
| eval AccessMask=Access_Mask
| eval ObjectType=Object_Type
| eval ObjectName=Object_Name
|table Account_Name,Logon_ID,Props,AccessMask,ObjectType, ObjectName]
| table time, Logon_ID, Account_Name, Props, AccessMask, ObjectType, ObjectName, DN, GUID, Value, Class
|stats values by Value
Figure 84 - Detecting Changes to the Domain (1)
Figure 85 - Detecting Changes to the Domain (2)
This is a decent query; however, it still has some issues—namely, that the detection itself does not only detect changes made to objects for our DCSync rights, but also other changes made to the domain. For example, when we modified the dacled.egg user to also have user create rights, as well as the DCSync rights on the domain, the query was triggered. The top portion shows the DCSync rights changes, and the bottom portion shows the change that was triggered when we added the user create rights.
Figure 86 - Additional Events
To build a higher fidelity detection, we need to address the ‘Value’ field and attempt to understand the hieroglyphic text that are SDDLs.
In order to filter down the detection output to include changes that only identify the addition/removal of DCSync rights added to the domain for a user/computer, we added the following SDDL prefix strings to each of the DCSync rights GUIDs. This effectively captured the addition of the DCSync rights but excluded changes made to other attributes, such as "User Creation" rights added to the domain root.
The SDDL string values to note here are:
- OA - Object Access Allowed
- CI - Container Inherit; DirectoryObjectControlAccess.
- CC - DirectoryObjectCreateChild
- CR - All Extended Rights
And if we translate the SDDL using our SDDL conversion tool, we can see that the permissions for the dacled.egg attribute were in fact made to the domain.
Figure 87 - SDDL Editor String for DCSync
With a slight modification, we were able build an additional query that did not pick up the “User Creation” right that added an additional search value looking for the string OA;CI;CR;
.
Figure 88 - Comparison Post Right User Creation Added
However, the query still identified when the DCSync rights were added.
Figure 89 - Comparison Post DCSync Added
4.6.3.4Detection with Event ID 5136 and 4662 (Improved Filtering)
And then lastly, one (1) final change was made to the query—instead of using only the OA;CI;CR; as the filter parameter, it was expanded to include the DCSync rights objects.
index=main ((EventCode=5136 Class=domainDNS DN="DC=BREAKFASTLAND,DC=LOCAL" AND Value=*(OA;CI;CR;89e95b76-444d-4c62-991a-0facbeda640c)(OA;CI;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2)(OA;CI;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2)*) OR (index=main Account_Name!=*$ Object_Type="%{19195a5b-6da0-11d0-afd3-00c04fd930c9}" Object_Name="%{754fb287-55d2-4d68-b7fc-0332e1746740}" EventCode=4662 Access_Mask = 0x40000))
| eval Logon_ID=if(EventCode==4662,mvindex(Logon_ID,-1), mvindex(Logon_ID,-1))
| eval user=if(EventCode==4662,mvindex( Account_Name,-1), mvindex( Account_Name,-1))
| eval DACL=if(EventCode==5136,mvindex( Value,-1), mvindex( Value,-1))
| join type=outer Logon_ID
[ search index=main Account_Name!=*$ Object_Type="%{19195a5b-6da0-11d0-afd3-00c04fd930c9}" Object_Name="%{754fb287-55d2-4d68-b7fc-0332e1746740}" EventCode=4662 Access_Mask = 0x40000
| eval Props=Properties
| eval AccessMask=Access_Mask
| eval ObjectType=Object_Type
| eval ObjectName=Object_Name
|table Account_Name,Logon_ID,Props,AccessMask,ObjectType, ObjectName]
| table time, LogonID, Account_Name, Props, AccessMask, ObjectType, ObjectName, DN, GUID, DACL, Class
|stats values by Logon_ID
Figure 90 - Final DCSync Query (1)
Figure 91 - Final DCSync Query (2)
5 Conclusion of Part 1B
While this isn’t the end of this series (stay tuned for parts 2 and 3), there are a few key takeaways from this post that are important to note. Most notably, while Event ID 5136 is the core event throughout all the detections that were built throughout Parts 1A through 3, there are accompanying events that are equally important to these detections. Event ID 4624 will give you a source IP, while Event ID 4662 can provide context to the detection.
It's also important to note that in most cases, multiple queries were built for each detection where possible. This is because it is critical to have multiple points of detection to fall back on in case attackers manage to develop new attacks that may not trigger one (1) solitary detection. Supplementally, most if not all queries in this post should be tweaked to account for differing GUIDs and objects within your own domain, while some will remain the same, such as the GUIDs for the DCSync Rights objects. Other methods of detection can also be incorporated here, such as PowerShell event logging, and while we were unable to find these detections in LDAP logs, they could possibly exist in that telemetry as well.
And lastly, it is important to remember that for SIEMs that may not have the advanced query language that exists within Splunk, all these queries can be broken into smaller sections or multiple queries.
Due to the length, this post has been split into two sections (Part1A and Part1B). Please see this link for a total PDF version of Part 1.
Lastly, this blog would not have been possible without help from the following people:
Charlie Bromberg (@_nwodtuhs)
Jonathan Johnson (@jsecurity101)
Jim Sykora (@jimsycurity)
Kelsey Segrue (@KelseySegrue)
6 References:
https://www.thehacker.recipes/ad/movement/dacl
Windows Events:
https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4662
https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4624
https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-5145
https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4742
https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4738
msDS-AllowedtoActOnBehalfOfOtherIdentity:
https://learn.microsoft.com/en-us/windows/win32/adschema/a-msds-allowedtoactonbehalfofotheridentity
https://jsecurity101.medium.com/defending-the-three-headed-relay-17e1d6b6a339
Service Principal Name (SPN):
https://learn.microsoft.com/en-us/windows/win32/ad/service-principal-names
https://learn.microsoft.com/en-us/windows/win32/ad/mutual-authentication-using-kerberos
https://github.com/fortra/impacket
https://github.com/fortra/impacket/blob/master/examples/GetUserSPNs.py
https://github.com/Kevin-Robertson/Powermad
msDS-AllowedtoDelegateTo:
https://learn.microsoft.com/en-us/windows/win32/adschema/a-msds-allowedtodelegateto
https://skyblue.team/posts/delegate-krbtgt/
https://csandker.io/2020/02/10/KerberosDelegationAWrapUp.html
msDS-KeyCredentialLink:
https://cyberstoph.org/posts/2022/03/detecting-shadow-credentials/
ScriptPath:
https://learn.microsoft.com/en-us/windows/win32/adschema/a-scriptpath
msTSInitalProgram:
https://learn.microsoft.com/en-us/windows/win32/adschema/a-mstsinitialprogram
GPO:
https://learn.microsoft.com/en-us/windows/win32/adschema/c-grouppolicycontainer
https://github.com/Hackndo/pyGPOAbuse
https://github.com/X-C3LL/GPOwned
https://www.thehacker.recipes/ad/movement/group-policies
https://learn.microsoft.com/en-us/windows/win32/adschema/c-grouppolicycontainer
https://labs.withsecure.com/tools/sharpgpoabuse
AddMember:
https://www.thehacker.recipes/ad/movement/dacl/addmember
https://github.com/PowerShellMafia/PowerSploit
https://learn.microsoft.com/en-us/windows/win32/adschema/r-self-membership
https://learn.microsoft.com/en-us/windows/win32/adschema/a-member
ForceChangePassword:
https://www.thehacker.recipes/ad/movement/dacl/forcechangepassword
https://learn.microsoft.com/en-us/windows/win32/adschema/r-user-force-change-password
GrantOwnerShip:
https://www.thehacker.recipes/ad/movement/dacl/grant-ownership
LAPS/GMSA:
https://www.trustedsec.com/blog/splunk-spl-queries-for-detecting-gmsa-attacks/
https://www.trustedsec.com/blog/a-lapse-in-judgement/
https://learn.microsoft.com/en-us/powerquery-m/datetime-fromfiletime
https://adsecurity.org/?p=4367
https://learn.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2022-ps
DCSync:
https://github.com/fortra/impacket
https://www.alteredsecurity.com/post/a-primer-on-dcsync-attack-and-detection
https://www.thehacker.recipes/ad/movement/credentials/dumping/dcsync
msDS-GroupManagedServiceAccount/msDS-ManagedServiceAccount References:
https://woshub.com/group-managed-service-accounts-in-windows-server-2012/
https://blog.netwrix.com/2022/10/13/group-managed-service-accounts-gmsa/
PowerMad/Set-MachineAcccountAttribute:
https://github.com/Kevin-Robertson/Powermad
https://skyblue.team/posts/delegate-krbtgt/
Other:
An ACE in the Hole Stealthy Host Persistence via Security Descriptors [Corrected Audio]
https://specterops.io/wp-content/uploads/sites/3/2022/06/an_ace_up_the_sleeve.pdf