A Hitch-Hacker's Guide To DACL-Based Detections - The Addendum
This blog was co-authored by TAC Practice Lead Megan Nilsen and Andrew Schwartz.
1 Introduction
Last year, Andrew and I posted a four (4) part blog series covering various Active Directory (AD) attributes and how modifications made by an attacker with sufficient privileges can be detected.
After 40+ detections covering over 20 different AD attributes- naturally, you think we’d be done.
Nah.
Most attackers and defenders alike would agree that the attack surface within AD is nearly limitless, and, as such, we identified a few additional attributes that we thought had value, particularly regarding detection and baselining opportunities.
As a reminder, this blog (series) assumes an 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. We will not examine any post exploitation (i.e., forged Kerberos tickets, etc.) as our primary purpose is to build detections identifying when changes are made.
Lastly, this post will provide classic Splunk SPL queries for detecting the attacks outlined, using only Windows Event IDs as described. Furthermore, this blog post only examines a subset of the Windows Event logging data, and not all possible telemetry within this data set have been analyzed.
2 Logging Setup
As noted in previous DACL posts (see 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
Though the length of this post is shorter than previous DACL posts, 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 Operating-System
4.1.1 Background
The operatingSystem attribute stores the name of the machine operating system in AD.
While the operatingSystem attribute may have limited use in attack application, it can be used for baselining the operating systems within a given AD environment, potentially identifying vulnerable devices. Alternately, this value could be changed in order to create honeypots that may attract attackers seeking to exploit vulnerable hosts on the network.
4.1.2 Modifying the Attributes (Attack)
As done in Parts 1A-3 of the previously released DACL series (Part 1A, 1B, 2, 3), we will use Kevin Robertson’s Powermad to modify the attribute for the previously created IMPOSTER-GRANOLA machine account.
Set-MachineAccountAttribute -Attribute OperatingSystem -Value NetApp
4.1.3 Building the Detection
4.1.3.1 Detection With Event IDs 5136, 4624, and 4662
index=main ((EventCode=5136 AND LDAP_Display_Name=operatingSystem) 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_Account=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, Mod_Account, Source_Network_Address , Class, DN, Logon_ID, Type, LDAP_Display_Name, Changed_Account, AccessMask, Props, Object_Properties
| where len(Class)>0
| stats values by _time, Changed_Account
4.2 Operating-System-Version
4.2.1 Background
The operatingSystemVersion attribute contains the value for the operating system’s version string. Microsoft provides a table that links the string version number to the normalized operating system here.
There is additional benefit in using this attribute, in tandem with the operatingSystem attribute, to compare the explicitly named operating system within the operatingSystem attribute to the string contained within operatingSystemVersion, thus potentially identifying differences that may indicate the value was tampered with, providing a useful IOC for defensive security professionals.
4.2.2 Modifying the Attributes (Attack)
As previously, we will utilize PowerMad to modify the attribute.
4.2.3 Building the Detection
4.2.3.1 Detection With Event IDs 5136, 4624, and 4662
index=main ((EventCode=5136 AND LDAP_Display_Name=operatingSystemVersion) 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_Account=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, Mod_Account, Source_Network_Address , Class, DN, Logon_ID, Type, LDAP_Display_Name, Changed_Account, AccessMask, Props, Object_Properties
| where len(Class)>0
| stats values by _time, Changed_Account
4.2.4 A Note About operatingSystem and operatingSystemVersion
As an interesting note, while we were exploring modifications of the operatingSystem and operatingSystemVersion attributes, we also identified that when creating a machine account via ADUC, it will by default set both attributes for the machine as blank. This is different from when a real machine is created and added to the domain manually.
We tested this by building a new VM and adding it to the domain, and you can see that the operatingSystem and operatingSystemVersion attributes are populated.
As a direct comparison, we also created machine accounts using two (2) different tools—Impacket’s addcomputer.py and PowerMad. Both machine creations resulted in blank values for both attributes.
This provides some interesting value as another potential IOC that defenders can use with machine account creation events (and the detections within this post) for identifying machine accounts created by attackers.
4.3 dynamicObject/Entry-TTL
4.3.1 Background
Technically, dynamicObjects are not attributes, but rather are types of objects (user, group, or computer) that are impermanent within AD. This has significant value for an attacker with sufficient permissions to create objects on a domain, given that they can create an object with a set destruction time, making it more difficult for auditors and defenders to track attacker activity if the appropriate telemetry isn’t present.
This is done by way of an attribute set on the dynamic object created called EntryTTL. EntryTTL designates a set “time-to-live” for the object before the Domain will delete it, relocating it to /dev/null.
4.3.2 Modifying the Attributes (Attack)
In order to modify the attribute, we first need to create an ldf file on our machine.
The text of the file should contain the following (you will need to replace the DN and SAM Account Name with the appropriate values for your domain):
dn: CN=cold.cereal,CN=Users,DC=BREAKFASTLAND,DC=LOCAL
changetype: add
objectClass: user
objectClass: dynamicObject
entryTTL: 1800
sAMAccountName: cold.cereal
Next, we have to open up a command prompt and navigate to the path where the file is. The following command should be run:
Ldifde -v -i -f create_dynamic_object.ldf
4.3.3 Building the Detection
4.3.3.1 Detection With Event IDs 5136, 4624, and 4662
index=main ((EventCode=5136 AND LDAP_Display_Name=entryTTL) 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_Account=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, Mod_Account, Source_Network_Address , Class, DN, Logon_ID, Type, LDAP_Display_Name, Changed_Account, AccessMask, Props, Object_Properties
| where len(Class)>0
| stats values by _time, Changed_Account
4.4 User-Principal-Name
4.4.1 Background
The userPrincipalName attribute contains the UPN for a user’s login name based upon the standards described in RFC 822. This value typically maps to the user’s email address name.
4.4.2 Modifying the Attributes (Attack)
As previously, we will utilize PowerMad to modify the attribute.
4.4.3 Building the Detection
4.4.3.1 Detection With Event IDs 5136, 4624, and 4662
index=main ((EventCode=5136 AND LDAP_Display_Name=userPrincipalName) 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_Account=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, Mod_Account, Source_Network_Address , Class, DN, Logon_ID, Type, LDAP_Display_Name, Changed_Account, AccessMask, Props, Object_Properties
| where len(Class)>0
| stats values by Changed_Account
4.4.3.2 Detection With 4742 and 4624
index=main ((EventCode=4742 AND User_Principal_Name!="-") OR (EventCode=4624 AND Account_Name!="*$" AND Account_Name!="ANONYMOUS LOGON" AND Account_Name!="SYSTEM"))
| 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 Account_Info=if(EventCode==4742,mvindex(Message,-1), mvindex(Message,-1))
| rex field=Account_Info "(?<Changed_Account>(?ms)..........................................................................Account\s+Name.*?(Account\s+Name:\s+)(\w+..........))"
| join type=outer Logon_ID
[ search (EventCode=4742)
| stats count by Logon_ID, Account_Name
| table Account_Name,Logon_ID ]
| join type=outer Logon_ID
[ search (EventCode=4624)
| stats count by Logon_ID, Account_Name, Source_Network_Address
| table Account_Name,Logon_ID, Source_Network_Address,]
| table _time, Mod_Account, Source_Network_Address, Logon_ID, User_Principal_Name
| stats values by _time, Mod_Account,Source_Network_Address, Logon_ID, User_Principal_Name
| table _time, Mod_Account, Source_Network_Address, Logon_ID, User_Principal_Name
4.5 User-Certificate
The userCertificate attribute contains a DER-encoded X509v3 certificate that is assigned to a given user.
4.5.1 Modifying the Attribute
To modify this attribute, we utilized ADUC to manually add a hexadecimal string into the attribute editor. In this case, we are not submitting a DER-encoded string, but only a test value in order to trigger the change to the attribute we are targeting.
4.5.2 Building the Detection
4.5.2.1 Detection With Event IDs 5136, 4624, and 4662
index=main ((EventCode=5136 AND LDAP_Display_Name=userCertificate) 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_Account=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, Mod_Account, Source_Network_Address , Class, DN, Logon_ID, Type, LDAP_Display_Name, Changed_Account, AccessMask, Props, Object_Properties
| where len(Class)>0
| stats values by _time, Changed_Account
5 Conclusion
Once again, our hope is that from this series of blog posts, professionals and organizations are given detections and detection templates that can provide assistance in auditing baselines, and providing deeper coverage of AD attributes that may assist defenders in more quickly identifying abnormal or malicious activity.
Another key point to remember when trying to implement the detections provided in this post within your own SIEM environment is that all detections were built in a lab environment. A real-world production environment will require additional tuning to remove false positives.
While best practice and preference may be to audit all attributes, we recognize, understand, and operate within the constraints of SIEM licensing costs. We wanted to highlight and prioritize some of the more significant attacks/abuses and thus have not covered every single attribute.
And finally, another big thank you to all those who assisted with peering, reviewing, and providing suggestions to make this blog series as good as it could be:
Jonathan Johnson (@jsecurity101)
Jim Sykora (@jimsycurity)
References
Operating System Attribute:
https://learn.microsoft.com/en-us/windows/win32/adschema/a-operatingsystem
https://www.crowdstrike.com/blog/seven-common-microsoft-ad-misconfigurations-that-adversaries-abuse/
https://twitter.com/JosephRyanRies/status/1671655129290797056?s=20
https://twitter.com/JosephRyanRies/status/1648724225773953024?s=20
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-38023
https://www.cve.org/CVERecord?id=CVE-2022-38023
Operating System Version Attribute:
https://learn.microsoft.com/en-us/windows/win32/adschema/a-operatingsystemversion
https://learn.microsoft.com/en-us/windows/win32/sysinfo/operating-system-version
http://dloder.blogspot.com/2009/04/who-told-ad-what-os-im-running.html
Dynamic Object/EntryTTL Attribute:
https://learn.microsoft.com/en-us/windows/win32/adschema/a-entryttl
4.14. Creating a Dynamic Object - Active Directory Cookbook [Book] (oreilly.com)
userPrincipalName Attribute:
https://learn.microsoft.com/en-us/windows/win32/adschema/a-userprincipalname
UserCertificate: