Skip to Main Content
October 11, 2023

A Hitch-hacker's Guide to DACL-Based Detections (Part 1B)

Written by Megan Nilsen and Andrew Schwartz
Active Directory Security Review Purple Team Adversarial Detection & Countermeasures Research Security Testing & Analysis Threat Hunting

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.

Image045

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.

Image046

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.

Image047

Figure 47 - Adding imposter.oatmeal to Domain Admins

Image048

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
Image049 1

Figure 49 - AddMember Final Query (1)

Image050 2

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.

Image051

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
Image052 1

Figure 52 - Detecting Addition of Account to Domain Admins Group (1)

Image053 2

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. 

Image054

Figure 54 - Adding Privileges to Account

Now we can utilize PowerSploit to change the password of our victim account.

Image055

Figure 55 - Changing Account Password with PowerSploit

Image056

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
Image057 1

Figure 57 - Final Query Detecting ForceChangePassword Modification (1)

Image058 2

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.

Image059

Figure 59 - Modifying User Account for WriteOwner

Then we use PowerView to set ownership.

Image060

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.

Image061 1

Figure 61 - Detecting Misconfiguration of User Object (1)

Image062 2

Figure 62 - Detecting Misconfiguration of User Object (2)

Image063

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.

Image064

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)
Image065

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.

Image066

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
Image067 1

Figure 67 - Detection With Event IDs 5136, 4624, 4662 (1)

Image068 2

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
Image069 1

Figure 69 - Detection Excluding Machine Accounts (1)

Image070 2

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.

Image071

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.

Image072

Figure 72 - Modifying the gMSA Account

Get-ADServiceAccount -Identity granola.bar -Properties *
Image073

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
Image074 1

Figure 74 - Detection With Event IDs 4662, 5136 and 4624 (1)

Image075 2

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
Image076

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
Image077

Figure 77 - Adding DCSync Rights

If we look at our privileges now, we can see the new rights reflected:

Image078

Figure 78 - Account SID

Image079

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.

Image080

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 
Image081 1

Figure 81 - Basic DCSync Query (1)

Image082 2

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 
Image083

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 
Image084 1

Figure 84 - Detecting Changes to the Domain (1)

Image085 2

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.

Image086

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.

Table Part1 B

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.

 

Image087

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;.

Image088

Figure 88 - Comparison Post Right User Creation Added

However, the query still identified when the DCSync rights were added.

Image089

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
Image090 1

Figure 90 - Final DCSync Query (1)

Image091 2

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://www.semperis.com/blog/spn-jacking-an-edge-case-in-writespn-abuse/
https://blog.harmj0y.net/activedirectory/targeted-kerberoasting/

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://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ada2/45916e5b-d66f-444e-b1e5-5b0666ed4d66

https://posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab

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://wald0.com/?p=179

https://serverfault.com/questions/692772/group-managed-service-accounts-principalsallowedtoretrievemanagedpassword

https://serverfault.com/questions/692772/group-managed-service-accounts-principalsallowedtoretrievemanagedpassword

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/openspecs/windows_protocols/ms-ada2/60acc5e9-e6dc-481f-a3ff-2cb763ab2d33

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

https://itconnect.uw.edu/tools-services-support/it-systems-infrastructure/msinf/other-help/understanding-sddl-syntax/

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://stackoverflow.com/questions/39226518/filtering-only-second-account-name-in-windows-event-log-using-a-regex

https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties

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