Read: 13 mins.
Print Friendly, PDF & Email

Auditing Group Changes

At your company, you may have run into requests to find out who made a change to an Active Directory (AD) Group, including which users and groups were added and removed. I have generally been asked to investigate after a security breach occurred or things suddenly stopped working. In this post, I will show you how to enable auditing of group changes, what Windows EventIDs to look for,  and how Splunk can be used to more easily find the relevant information. Specifically, we will focus on Security-enabled AD and non-AD Groups.

Related: For investigating Account-related events, see my Account Lockouts and Modifications post. Visualize Account Lockout events with my AD Lockout Splunk Dashboards to graphically identify patterns.

Active Directory Groups

Microsoft’s Active Directory (AD) is a service that governs how resources can be utilized by a collection of users, groups, and computers. Enterprises use AD to authenticate, authorize, secure, and audit access within a security boundary — a Domain — to file servers, computers, emails, and more. You are given a user account (often referred to as your “network login”) to access what has been made available to you. A Domain Controller (DC) is the server that contains a copy of the AD database and is responsible for the replication of said data between all other DCs within the Domain.

To secure access to a company’s resources, such as file shares, permissions can be granted to Active Directory accounts and groups. A group is a collection of members (such as users and other groups) to help make administration easier.

Group Scopes and Types

There are three Scopes of Active Directory groups:

  • Domain Local Group, sometimes referred to as “Local Group”
    • Do not confuse with “Builtin Local” groups
  • Global Group
  • Universal Group

Each scope is used for a specific purpose, can be applied to particular areas of the Domain or Forest, and generally, access should be granted using the “A-G-DL-P” method.

For each Scope, there are two Types:

  • Distribution – For email distribution lists
  • Security – For assigning access to resources

You can change groups to/from different Scopes and Types, but do so with extreme caution.

Tips from the Field

  • Scope Changes
    • Domain Local can only directly change to Universal
    • Domain Local can be changed to Global Group by first turning it to Universal
    • Global Group can only directly change to Universal
    • Universal Group can change directly to Domain Local or Global
  • Create a separate Distribution group for distribution lists, and a Security group for controlling permissions
    • Minimize the use of the same, mail-enabled Security group for managing permissions and as a distribution list

Builtin Local (Non-AD) Groups

The “Builtin Local” groups are NOT Active Directory Domain groups, their Scope and Type cannot be changed, and they exist locally on the Domain Controller, Member Server (Domain-joined), and Workstation.

  • Builtin Local group examples
    • Member Server/Workstation
      • Administrators, Power Users, Users, Remote Desktop Users, Hyper-V Administrators, Backup Operators, Cryptographic Operators, Network Configuration Operators, etc.
    • Domain Controller – Same as Member Server/Workstation plus:
      • Account Operators, Terminal Server License Servers, Print Operators, etc.

Enable Auditing

Before we can perform forensics on group changes, we have to turn on what to audit in Active Directory. This can and will increase the amount of data stored by the Domain Controllers’ Event Logs and why auditing for most events is disabled by default. Reference Microsoft’s Security Auditing documentation and determine what your organization wants and needs to log. You may want to start with just a few items to become familiar with policy configuration before opening the flood gates.

If your company has the ability to send the audit logs to a SIEM (Security Information and Event Management) instead, such as Splunk, you may want to utilize that to aid with better, faster, and deeper, investigative searches.

Event IDs

Active Directory changes and incidents are stored in Event Logs with a code: the Event ID. This allows one to more quickly search for just the data you need. Below are the Event IDs that relate to Active Directory Security Groups and what they are for. For additional details, go to Microsoft’s Audit Security Group Management documentation.

Group Changes: Type or Scope

  • 4764 – Group Type (or Scope) changed
    • According to Microsoft, this ID only occurs when a Group’s Type (regardless of its Scope) is changed
    • In my testing, however, this ID was also recorded when a Group’s Scope changed
    • Examples
      • Type change: Universal Security <> Universal Distribution
      • Scope change: Universal Security <> Global Security
      • Scope change: Universal Distribution <> Domain Local Distribution

Despite Microsoft’s Documentation indicating Event ID 4764 only applying to Group Type changes, my tests found it also occurring for Group Scope modifications.

SECURITY-Enabled Group Changes

Caution: During the course of an investigation, be aware that the Event IDs listed below ONLY apply to Security (not Distribution) Groups.

  • Example: Creation of a Universal Distribution Group does NOT log Event ID 4754 — but a Universal Security Group would

Security Group: Creation, Deletion, Change

  • 4731 Created, 4734 Deleted, 4735 Changed – Domain Local or Builtin Local group
  • 4727 Created, 4730 Deleted, 4737 Changed – Global Group
  • 4754 Created, 4758 Deleted, 4755 Changed – Universal Group

When a Group’s Scope is changed, the NEW Scope’s Event ID is recorded. Example: Universal to Global triggers ID 4737. This Event may also occur with other changes, such as the discretionary access control list (DACL), but not all.

Security Group: Membership Change

  • 4732 Added, 4733 Removed – Domain Local or Builtin Local group member
  • 4728 Added, 4729 Removed – Global Group member
  • 4756 Added, 4757 Removed – Universal Group member

Caution: For forensic purposes, be aware that when a group member is deleted from AD, its resulting removal from any Group(s) is NOT recorded with an event. Event ID 4733, 4729, or 4757 is only triggered if the member was directly removed from a group, but not as an indirect result of it getting deleted from AD itself.

Other Security Group Events

  • 4799 – Domain Local or Builtin Local group enumerated by a process (“Active Directory Users and Computers” tool does not trigger this event)
    • Enumerating a group means its membership was read

Splunk Queries

Splunk is a VERY powerful, expensive tool that aggregates logs from multiple sources (such as systems, applications, network devices, and more) to allow you to search, monitor, and analyze a wealth of Big Data. It is a very useful SIEM (Security Information and Event Management) tool that can also be used to deconstruct a timeline of events, such as a breach in the network.

Splunk results can be downloaded as CSV, XML, and JSON files for further reporting and analysis. They can also be printed as PDF, although at the time of writing, the orientation is locked in Portrait mode

Oddball Event ID: 4756

Bear with me here. This will get a bit confusing. I came across a possible bug with Event ID 4756. All Security Group-related Event IDs (4732, 4733, 4728, 4729, 4757, 4731, etc.) refer to groups with “Group Name” and “Group Domain” under the “Group” header, as shown below. Only 4756 goes with “Account Name” and “Account Domain”:

Note: “Security ID” is consistent across all Security Group-related Event IDs, including 4756. I will provide a more simplified query using “Security ID” for each example, if you do not need to separate Group/Account samAccountName and Domain into their own, sortable fields.

Example: Instead of outputting GroupDomain = “YUENX” and Group = “Mike-4756”, using “Security ID” will output Group as “YUENX\Mike-4756”.

To work around the uniqueness of ID 4756 using “Account” instead of “Group”, I added extra RegExes to the queries found in this article, as shown below. If you choose to use the “Security_ID”-based query examples instead, you do not need to implement a workaround (see the “Note” above).

| rex "Group:\s+[^\n]+\s+Group Name:\s+(?<Group>.*)"
| rex "Group:\s+[^\n]+\s+[^\n]+\s+Group Domain:\s+(?<GroupDomain>.*)"
`comment("4756 uses 'Account', not 'Group'")`
| rex "Group:\s+[^\n]+\s+Account Name:\s+(?<Group>.*)"
| rex "Group:\s+[^\n]+\s+[^\n]+\s+Account Domain:\s+(?<GroupDomain>.*)"

Search for Group Membership Changes (ie. Domain Administrators)

This query will locate any membership changes to Privileged AD groups (like Domain Administrators) within the last 30 days, including who made the change and who was added or removed.

Group Membership Changes

Splunk queries for outputting samAccountName and Domain as SEPARATE fields or COMBINED:

index="*" earliest=-30d latest=now() source=WinEventLog:Security
(Group_Name="Administrators" OR Group_Name="Enterprise Admins" OR Group_Name="Domain Admins" OR Group_Name="Schema Admins" OR Group_Name="Account Operators" OR Group_Name="Backup Operators" OR Group_Name="Server Operators" OR Group_Name="DHCP Administrators" OR Group_Name="DnsAdmins" OR Group_Name="Network Configuration Operators" OR Group_Name="Hyper-V Administrators" OR Group_Name="Domain Controllers" OR Group_Name="Read-only Domain Controllers" OR Group_Name="Cryptographic Operators" OR Group_Name="Cert Publishers")
(EventCode=4728 OR EventCode=4729 OR EventCode=4732 OR EventCode=4733 OR EventCode=4756 OR EventCode=4757)
| eval Time=strftime(_time, "%m/%d/%y %H:%M:%S") | sort -_time
| eval EventCode=case(EventCode==4728, "4728 [+] GG Sec", EventCode==4729, "4729 [-] GG Sec", EventCode==4732, "4732 [+] DL/BL Sec", EventCode==4733, "4733 [-] DL/BL Sec", EventCode==4756, "4756 [+] UG Sec", EventCode==4757, "4757 [-] UG Sec", 1=1, EventCode)
| rex "Subject:\s+[^\n]+\s+Account Name:\s+(?<ActionBy>.*)" | rex "Subject:\s+[^\n]+\s+[^\n]+\s+Account Domain:\s+(?<ActionByDomain>.*)"
| rex "Member:\s+\w+\s\w+:\s+(?<Member>.*)" | rex "Member:\s+Security ID:[^\n]+\s+Account Name:\s+(?<MemberDN>.*)"
| rex "Group:\s+[^\n]+\s+Group Name:\s+(?<Group>.*)" | rex "Group:\s+[^\n]+\s+[^\n]+\s+Group Domain:\s+(?<GroupDomain>.*)"
`comment("4756 uses 'Account', not 'Group'")` | rex "Group:\s+[^\n]+\s+Account Name:\s+(?<Group>.*)" | rex "Group:\s+[^\n]+\s+[^\n]+\s+Account Domain:\s+(?<GroupDomain>.*)"
| rename ComputerName as "Computer", name as "EventDescription"
| table Time, GroupDomain, Group, EventCode, Member, MemberDN, ActionBy, ActionByDomain, Computer, EventDescription
index="*" earliest=-30d latest=now() source=WinEventLog:Security
(Group_Name="Administrators" OR Group_Name="Enterprise Admins" OR Group_Name="Domain Admins" OR Group_Name="Schema Admins" OR Group_Name="Account Operators" OR Group_Name="Backup Operators" OR Group_Name="Server Operators" OR Group_Name="DHCP Administrators" OR Group_Name="DnsAdmins" OR Group_Name="Network Configuration Operators" OR Group_Name="Hyper-V Administrators" OR Group_Name="Domain Controllers" OR Group_Name="Read-only Domain Controllers" OR Group_Name="Cryptographic Operators" OR Group_Name="Cert Publishers")
(EventCode=4728 OR EventCode=4729 OR EventCode=4732 OR EventCode=4733 OR EventCode=4756 OR EventCode=4757)
| eval Time=strftime(_time, "%m/%d/%y %H:%M:%S") | sort -_time
| eval EventCode=case(EventCode==4728, "4728 [+] GG Sec", EventCode==4729, "4729 [-] GG Sec", EventCode==4732, "4732 [+] DL/BL Sec", EventCode==4733, "4733 [-] DL/BL Sec", EventCode==4756, "4756 [+] UG Sec", EventCode==4757, "4757 [-] UG Sec", 1=1, EventCode)
| eval "ActionBy"=mvindex(Security_ID, 0) | eval "Member"=mvindex(Security_ID, 1) | eval "Group"=mvindex(Security_ID, 2)
| rex "Member:\s+Security ID:[^\n]+\s+Account Name:\s+(?<MemberDN>.*)"
| rename ComputerName as "Computer", name as "EventDescription"
| table Time, Group, EventCode, Member, MemberDN, ActionBy, Computer, EventDescription

The “[+]” and “[-]” symbols signify a member as having been added or removed, respectively. “BL”, “DL”, “GG”, and “UG” refer to Builtin Local, Domain Local, Global, and Universal Group.

Tips:

  • For faster results, replace “index=*” with the appropriate index to search through
    • You can find the list of available indexes by querying: “| eventcount summarize=false index=* index=_* | dedup index | fields index
  • Commands are case-sensitive
  • “1=1, EventCode” means: Default = EventCode itself (to catch any value not matching any Case evaluations)
  • To output only changes made to a specific Member within the Group, add this before the “| table” line:
    | search Member=”DOMAIN\\user”

Modify the “Group_Name” line to look for events related to other AD groups:

  • Group_Name=”vpn_*” – Any AD group starting with “VPN_” (not case sensitive)
  • Group_Name=”Marketing Users” – Changes to the “Marketing Users” AD group

For changes made to Builtin Local or non-AD groups local to computers, add “Group_Domain” to the query:

  • “Users” group on “Computer01” computer: Group_Domain=”Computer01″ AND Group_Name=”Users”
  • “Administrators” of the Builtin Local group: Group_Domain=”Builtin” AND Group_Name=”Administrators”
    • Omitting Group_Domain will work too: Group_Name=”Administrators”

Membership Changes Made TO and BY an Account

There were two searches I wanted to create for finding membership changes:

  • What was done TO an account (a user)?
  • What was done BY an account (an administrator)?

However, creating the queries were not immediately obvious because there are multiple matches for both “Account Name” and “Security ID” in the Event message. Look closer at “Account Name” and you will see that one is in sAMAccountName (Subject) and the other in DistinguishedName (Member) format. With that in mind, we are now able to accomplish the goal I set out for. See the ‘Note on Multiple Matches‘ section below for an explanation.

Changes Made TO an Account

To search for any Group Membership changes made TO an account, you will need to use its DistinguishedName for “Account_Name”.

Splunk queries for outputting samAccountName and Domain as SEPARATE fields or COMBINED:

index="*" earliest=-30d latest=now() source=WinEventLog:Security
Account_Name="CN=Mike Yuen,OU=Test,DC=YuenX,DC=com"
(EventCode=4728 OR EventCode=4729 OR EventCode=4732 OR EventCode=4733 OR EventCode=4756 OR EventCode=4757)
| eval Time=strftime(_time, "%m/%d/%y %H:%M:%S") | sort -_time
| eval EventCode=case(EventCode==4728, "4728 [+] GG Sec", EventCode==4729, "4729 [-] GG Sec", EventCode==4732, "4732 [+] DL/BL Sec", EventCode==4733, "4733 [-] DL/BL Sec", EventCode==4756, "4756 [+] UG Sec", EventCode==4757, "4757 [-] UG Sec", 1=1, EventCode)
| rex "Subject:\s+[^\n]+\s+Account Name:\s+(?<ActionBy>.*)" | rex "Subject:\s+[^\n]+\s+[^\n]+\s+Account Domain:\s+(?<ActionByDomain>.*)"
| rex "Member:\s+\w+\s\w+:\s+(?<Member>.*)" | rex "Member:\s+Security ID:[^\n]+\s+Account Name:\s+(?<MemberDN>.*)"
| rex "Group:\s+[^\n]+\s+Group Name:\s+(?<Group>.*)" | rex "Group:\s+[^\n]+\s+[^\n]+\s+Group Domain:\s+(?<GroupDomain>.*)"
`comment("4756 uses 'Account', not 'Group'")` | rex "Group:\s+[^\n]+\s+Account Name:\s+(?<Group>.*)" | rex "Group:\s+[^\n]+\s+[^\n]+\s+Account Domain:\s+(?<GroupDomain>.*)"
| rename ComputerName as "Computer", name as "EventDescription"
| table Time, GroupDomain, Group, EventCode, Member, MemberDN, ActionBy, ActionByDomain, Computer, EventDescription
index="*" earliest=-30d latest=now() source=WinEventLog:Security
Account_Name="CN=Mike Yuen,OU=Test,DC=YuenX,DC=com"
(EventCode=4728 OR EventCode=4729 OR EventCode=4732 OR EventCode=4733 OR EventCode=4756 OR EventCode=4757)
| eval Time=strftime(_time, "%m/%d/%y %H:%M:%S") | sort -_time
| eval EventCode=case(EventCode==4728, "4728 [+] GG Sec", EventCode==4729, "4729 [-] GG Sec", EventCode==4732, "4732 [+] DL/BL Sec", EventCode==4733, "4733 [-] DL/BL Sec", EventCode==4756, "4756 [+] UG Sec", EventCode==4757, "4757 [-] UG Sec", 1=1, EventCode)
| eval "ActionBy"=mvindex(Security_ID, 0) | eval "Member"=mvindex(Security_ID, 1) | eval "Group"=mvindex(Security_ID, 2)
| rex "Member:\s+Security ID:[^\n]+\s+Account Name:\s+(?<MemberDN>.*)"
| rename ComputerName as "Computer", name as "EventDescription"
| table Time, Group, EventCode, Member, MemberDN, ActionBy, Computer, EventDescription

Changes Made BY an Account

To search for any Group Membership changes made BY an account, you will need its: sAMAccountName and DistinguishedName. If you use just the sAMAccountName, the result will be changes made TO and BY the account. See the ‘Note on Multiple Matches‘ section below for an explanation.

Caution: The ORDER is VERY important here. Account_Name must first be sAMAccountName, then DistinguishedName. If you reverse the order, the result will be entirely different because of Account_Name having multiple matches within the same Event message.

Use the same query as “Changes Made TO an Account” (above), except change the “Account_Name” line with:

Account_Name=Mike.Yuen NOT Account_Name="CN=Mike Yuen,OU=Test,DC=YuenX,DC=com"

Search for Group Changes

This query will locate AD group creations, deletions, changes, and AD group Type changes within the last 30 days.

Group Adds, Deletes, Changes

Splunk queries for outputting samAccountName and Domain as SEPARATE fields or COMBINED:

index="*" earliest=-30d latest=now() source=WinEventLog:Security
Group_Name="Domain *"
(EventCode=4764 OR EventCode=4731 OR EventCode=4734 OR EventCode=4735 OR EventCode=4727 OR EventCode=4730 OR EventCode=4737 OR EventCode=4754 OR EventCode=4758 OR EventCode=4755)
| eval Time=strftime(_time, "%m/%d/%y %H:%M:%S") | sort -_time
| eval EventCode=case(EventCode==4799, "4799 (e) DL/BL Sec ENUM", EventCode==4764, "4764 (x) Type/Scope CHANGE", EventCode==4731, "4731 (a) DL/BL Sec CREATE", EventCode==4734, "4734 (d) DL/BL Sec DELETE", EventCode==4735, "4735 (c) DL/BL Sec CHANGE", EventCode==4727, "4727 (a) GG Sec CREATE", EventCode==4730, "4730 (d) GG Sec DELETE", EventCode==4737, "4737 (c) GG Sec CHANGE", EventCode==4754, "4754 (a) UG Sec CREATE", EventCode==4758, "4758 (d) UG Sec DELETE", EventCode==4755, "4755 (c) UG Sec CHANGE", 1=1, EventCode)
| rex "Subject:\s+[^\n]+\s+Account Name:\s+(?<ActionBy>.*)" | rex "Subject:\s+[^\n]+\s+[^\n]+\s+Account Domain:\s+(?<ActionByDomain>.*)"
| rex "Group:\s+[^\n]+\s+Group Name:\s+(?<Group>.*)" | rex "Group:\s+[^\n]+\s+[^\n]+\s+Group Domain:\s+(?<GroupDomain>.*)"
`comment("4756 uses 'Account', not 'Group'")` | rex "Group:\s+[^\n]+\s+Account Name:\s+(?<Group>.*)" | rex "Group:\s+[^\n]+\s+[^\n]+\s+Account Domain:\s+(?<GroupDomain>.*)"
| rename ComputerName as "Computer", name as "EventDescription", Change_Type as "4764_Note"
| table Time, GroupDomain, Group, EventCode, ActionBy, ActionByDomain, Computer, EventDescription, 4764_Note
index="*" earliest=-30d latest=now() source=WinEventLog:Security
Group_Name="Domain *"
(EventCode=4764 OR EventCode=4731 OR EventCode=4734 OR EventCode=4735 OR EventCode=4727 OR EventCode=4730 OR EventCode=4737 OR EventCode=4754 OR EventCode=4758 OR EventCode=4755)
| eval Time=strftime(_time, "%m/%d/%y %H:%M:%S") | sort -_time
| eval EventCode=case(EventCode==4799, "4799 (e) DL/BL Sec ENUM", EventCode==4764, "4764 (x) Type/Scope CHANGE", EventCode==4731, "4731 (a) DL/BL Sec CREATE", EventCode==4734, "4734 (d) DL/BL Sec DELETE", EventCode==4735, "4735 (c) DL/BL Sec CHANGE", EventCode==4727, "4727 (a) GG Sec CREATE", EventCode==4730, "4730 (d) GG Sec DELETE", EventCode==4737, "4737 (c) GG Sec CHANGE", EventCode==4754, "4754 (a) UG Sec CREATE", EventCode==4758, "4758 (d) UG Sec DELETE", EventCode==4755, "4755 (c) UG Sec CHANGE", 1=1, EventCode)
| eval "ActionBy"=mvindex(Security_ID, 0) | eval "Member"=mvindex(Security_ID, 1) | eval "Group"=mvindex(Security_ID, 2)
| rename ComputerName as "Computer", name as "EventDescription", Change_Type as "4764_Note"
| table Time, Group, EventCode, ActionBy, Computer, EventDescription, 4764_Note

Tip: For a more compact view, remove “4764_Note” from the “table” line

For changes made to Builtin Local or non-AD groups local to computers, add “Group_Domain” to the query:

  • “Users” group on “Computer01” computer: Group_Domain=”Computer01″ AND Group_Name=”Users”
  • “Administrators” of the Builtin Local group: Group_Domain=”Builtin” AND Group_Name=”Administrators”
    • Omitting Group_Domain will work too: Group_Name=”Administrators”

Search for Group and Membership Changes

This query will comb through the last 30 days (within the “MyDomain” domain) to locate all 1) AD group membership changes, including who made the change and who was added or removed, 2) AD group creations, deletions, changes, and 3) AD group Type changes.

Membership Changes and Group Adds, Deletes, Changes

Splunk queries for outputting samAccountName and Domain as SEPARATE fields or COMBINED:

index="*" earliest=-30d latest=now() source=WinEventLog:Security
Group_Name="*" AND Group_Domain="MyDomain"
(EventCode=4764 OR EventCode=4731 OR EventCode=4734 OR EventCode=4735 OR EventCode=4727 OR EventCode=4730 OR EventCode=4737 OR EventCode=4754 OR EventCode=4758 OR EventCode=4755) OR
(EventCode=4728 OR EventCode=4729 OR EventCode=4732 OR EventCode=4733 OR EventCode=4756 OR EventCode=4757)
| eval Time=strftime(_time, "%m/%d/%y %H:%M:%S") | sort -_time
| eval EventCode=case(EventCode==4799, "4799 (e) DL/BL Sec ENUM", EventCode==4764, "4764 (x) Type/Scope CHANGE", EventCode==4731, "4731 (a) DL/BL Sec CREATE", EventCode==4734, "4734 (d) DL/BL Sec DELETE", EventCode==4735, "4735 (c) DL/BL Sec CHANGE", EventCode==4727, "4727 (a) GG Sec CREATE", EventCode==4730, "4730 (d) GG Sec DELETE", EventCode==4737, "4737 (c) GG Sec CHANGE", EventCode==4754, "4754 (a) UG Sec CREATE", EventCode==4758, "4758 (d) UG Sec DELETE", EventCode==4755, "4755 (c) UG Sec CHANGE", 1=1, EventCode)
| eval EventCode=case(EventCode==4728, "4728 [+] GG Sec", EventCode==4729, "4729 [-] GG Sec", EventCode==4732, "4732 [+] DL/BL Sec", EventCode==4733, "4733 [-] DL/BL Sec", EventCode==4756, "4756 [+] UG Sec", EventCode==4757, "4757 [-] UG Sec", 1=1, EventCode)
| rex "Subject:\s+[^\n]+\s+Account Name:\s+(?<ActionBy>.*)" | rex "Subject:\s+[^\n]+\s+[^\n]+\s+Account Domain:\s+(?<ActionByDomain>.*)"
| rex "Member:\s+\w+\s\w+:\s+(?<Member>.*)" | rex "Member:\s+Security ID:[^\n]+\s+Account Name:\s+(?<MemberDN>.*)"
| rex "Group:\s+[^\n]+\s+Group Name:\s+(?<Group>.*)" | rex "Group:\s+[^\n]+\s+[^\n]+\s+Group Domain:\s+(?<GroupDomain>.*)"
`comment("4756 uses 'Account', not 'Group'")` | rex "Group:\s+[^\n]+\s+Account Name:\s+(?<Group>.*)" | rex "Group:\s+[^\n]+\s+[^\n]+\s+Account Domain:\s+(?<GroupDomain>.*)"
| rename ComputerName as "Computer", name as "EventDescription", Change_Type as "4764_Note"
| table Time, GroupDomain, Group, EventCode, Member, MemberDN, ActionBy, ActionByDomain, Computer, EventDescription, 4764_Note
index="*" earliest=-30d latest=now() source=WinEventLog:Security
Group_Name="*" AND Group_Domain="MyDomain"
(EventCode=4764 OR EventCode=4731 OR EventCode=4734 OR EventCode=4735 OR EventCode=4727 OR EventCode=4730 OR EventCode=4737 OR EventCode=4754 OR EventCode=4758 OR EventCode=4755) OR
(EventCode=4728 OR EventCode=4729 OR EventCode=4732 OR EventCode=4733 OR EventCode=4756 OR EventCode=4757)
| eval Time=strftime(_time, "%m/%d/%y %H:%M:%S") | sort -_time
| eval EventCode=case(EventCode==4799, "4799 (e) DL/BL Sec ENUM", EventCode==4764, "4764 (x) Type/Scope CHANGE", EventCode==4731, "4731 (a) DL/BL Sec CREATE", EventCode==4734, "4734 (d) DL/BL Sec DELETE", EventCode==4735, "4735 (c) DL/BL Sec CHANGE", EventCode==4727, "4727 (a) GG Sec CREATE", EventCode==4730, "4730 (d) GG Sec DELETE", EventCode==4737, "4737 (c) GG Sec CHANGE", EventCode==4754, "4754 (a) UG Sec CREATE", EventCode==4758, "4758 (d) UG Sec DELETE", EventCode==4755, "4755 (c) UG Sec CHANGE", 1=1, EventCode)
| eval EventCode=case(EventCode==4728, "4728 [+] GG Sec", EventCode==4729, "4729 [-] GG Sec", EventCode==4732, "4732 [+] DL/BL Sec", EventCode==4733, "4733 [-] DL/BL Sec", EventCode==4756, "4756 [+] UG Sec", EventCode==4757, "4757 [-] UG Sec", 1=1, EventCode)
| eval "ActionBy"=mvindex(Security_ID, 0) | eval "Member"=mvindex(Security_ID, 1) | eval "Group"=mvindex(Security_ID, 2)
| rex "Member:\s+Security ID:[^\n]+\s+Account Name:\s+(?<MemberDN>.*)"
| rename ComputerName as "Computer", name as "EventDescription", Change_Type as "4764_Note"
| table Time, Group, EventCode, Member, MemberDN, ActionBy, Computer, EventDescription, 4764_Note

Tip: For a more compact view, remove “MemberDN” and “4764_Note” from the “table” line

Note on Multiple Matches

When an Event’s message body has multiple values for the same field, some challenges will be encountered. Look at the below screenshots of Event IDs 4732 and 4764. Notice that “Security ID” and “Account Name” have multiple values:

Now, let’s look at Event ID 4732 more closely. It occurred when “Michael.Yuen” (Member) was added to the Group, “Mike-Test”. The change was made by “Mike-Admin” (Subject).

The “Security ID” field occurs three times. If you were to reference it in your search, the result would be:

YUENX\Mike-Admin
YUENX\Michael.Yuen
YUENX\Mike-Test2

In other words, the “Security_ID” array contains 3 values, and each is assigned an index number (0-2, in this case).

  • Index 0 = “YUENX\Mike-Admin”, 1 = “YUENX\Michael.Yuen”, and 2 = “YUENX\Mike-Test”

Each index can be programmatically  obtained using “mvindex”:

  • “eval FirstId=mvindex(Security_ID,0)” assigns “FirstId” = “YUENX\Mike-Admin”
  • “eval SecondId=mvindex(Security_ID,1)” assigns “SecondId” = “YUENX\Michael.Yuen”
  • “eval ThirdId=mvindex(Security_ID,2)” assigns “ThirdId” = “YUENX\Mike-Test”

You may also utilize RegEx (learn it here) to grab the values you need instead.

Read on to see why being aware that a field may have multiple values becomes crucial in finding the data you need.

Note on “Membership Changes Made TO and BY an Account”

When “Account Name” occurred twice in Group Membership Change events, such as with ID 4732 (see screenshot below), trying to see what happened with one or the other presented challenges.

Thankfully, I noticed that the name format in both Account Name fields were different:

  • Under “Subject” header (marked with “0) is “Mike-Admin”, a sAMAccountName
  • Under “Member” header (marked with “1”) is “CN=Mike Yuen,OU=Admins,DC=YuenX,DC=com”, a DistinguishedName

With that observation in mind, I was now able to create the queries I needed. One would utilize for “Account_Name” its sAMAccountName and the other would DistinguishedName.

Bonus: Security Event IDs

Below are some Event IDs that could help with security investigations or audits. For other Events related to accounts and groups, see our Find Account Lockout Sources and Modifications article.

Event IDDescriptionType
4648Logon attempted with explicit credentials (ie. Scheduled Task or RunAs)Account
4720Account createdAccount
4722Account enabledAccount
4723Password change attemptedAccount
4724Password reset attemptedAccount
4725Account disabledAccount
4726Account deletedAccount
4738Object modifiedAccount
4740User Account locked outAccount
4740Account locked outAccount
4767Account unlockedAccount
4782Account's password hash accessedAccount
4946Windows Firewall exceptions: Rule addedFirewall
4947Windows Firewall exceptions: Rule modifiedFirewall
4950Windows Firewall setting changedFirewall
4954Windows Firewall group policy setting changedFirewall
5025Windows Firewall service stoppedFirewall
4728Global security-enabled Group user addedGroup
4732Local security-enabled Group user addedGroup
4735Local security-enabled Group modifiedGroup
4737Global security-enabled Group modifiedGroup
4755Universal security-enabled Group modifiedGroup
4756Universal security-enabled Group user addedGroup
4964Special Group assigned to a new logonGroup
1102Audit log cleared
Info
4616System time changedInfo
4719System audit policy changedInfo
4794DSRM (Directory Services Restore Mode) admin password set attemptInfo
5447Windows Filtering Platform filter changedInfo
12294Look for this event to search for potential attacks against the Administrator accountInfo
4657Registry value changedRegistry
4698Scheduled Task createdScheduled Task
4699Scheduled Task deletedScheduled Task
4700Scheduled Task enabledScheduled Task
4701Scheduled Task disabledScheduled Task
4702Scheduled Task updatedScheduled Task
4697Service installation attemptedService

General Audit? See our Find Account Lockout Sources and Modifications article for general event IDs related to accounts and groups.

Helpful?

Before I embarked on my mission to create Splunk queries to investigate Group Membership changes, I did not realize how involving it would become, prompting the write-up of this article. I hope that it will be useful in your security research.

Related: For investigating Account-related events, see my Account Lockouts and Modifications post. Visualize Account Lockout events with my AD Lockout Splunk Dashboards to graphically identify patterns.

Interesting Read: KRBTGT

Active Directory security is EXTREMELY important. One of the articles any domain administrator should read is one by Bryan Patton over at Quest. In it, he clearly described what KRBTGT is, what it is for, how it is used, and why its password should be changed regularly (and not too quickly). Go read up on it before your environment gets compromised! You can thank Bryan and I later.

Related Posts

Credits:
– Featured Image by Chang Duong via Unsplash