Before proceeding to this lab’s tasks make sure that you have studied “Detection of Active Directory attacks”, especially if you are not aware of how Active Directory attacks work.

Task 1. Hunt for brute force attacks inside Active Directory

For this task you will have to identify the events/artifacts/traces related to various brute force attacks inside an Active Directory environment. Specifically, hunt for:

  • S01D01 - Logon attempts using a non-existing account (Kerberos) - Look for events 4768 auditing Kerberos authentication ticket (TGT) requests and filters them on the Status field equal to code 0x6. This value means that the submitted username does not exist.
  • S01D02 - Logon attempts using a non-existing account (NTLM) - Look for events 4776 auditing NTLM authentication and filters them on the Status field equal to code 0xC0000064.
  • S01D03 - Excessive failed password attempts from one source (Kerberos) - Filter events 4771 with code 0x18 which means invalid pre-authentication information, usually a wrong password.
  • S01D04 - Excessive failed password attempts from one source (NTLM) - Look for events 4776 with status code 0xC000006A meaning wrong password.
  • S01D05 - Password spraying (Kerberos) - Look for events 4771 with code 0x18, group by source IP and count unique target accounts.
  • S01D06 - Password spraying (NTLM) - Look for events 4776 with status 0xC000006A, group by source workstation and count unique target accounts.
  • S01D07 - Multiple account lockouts from one source - Look for events 4740 (user account locked out), transaction by source computer.
  • S01D08 - Logon attempts towards disabled accounts (Kerberos) - Look for events 4768 with status 0x12 (account disabled).

S01D01 - Logon attempts using a non-existing account (Kerberos)

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4768 Status=0x6
| transaction IpAddress maxpause=5m maxevents=-1
| where eventcount > 5
| eval Source=if(IpAddress=="::1", Computer, IpAddress)
| eval accounts=mvcount(TargetUserName)
| where accounts > 2
| table _time, host, Source, TargetUserName, accounts, eventcount
| sort - _time
| rename _time AS "Time", host AS "Host", TargetUserName AS "Target Username", accounts AS "Number of Accounts", eventcount AS "Total Attempts"
| convert ctime(Time)

This search identifies single source making non-existing account login attempts. Event 4768 with status 0x6 means the account does not exist. The transaction command groups events by source IP within a 5-minute window. Only transactions with more than 5 events and targeting more than 2 accounts are displayed.

How to Implement: Kerberos Authentication Service auditing needs to be enabled on domain controllers.

Known False Positives: Some applications may use outdated credentials.

S01D02 - Logon attempts using a non-existing account (NTLM)

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4776 Status=0xC0000064
| transaction Workstation maxpause=5m maxevents=-1
| where eventcount > 5
| eval accounts=mvcount(TargetUserName)
| where accounts > 2
| table _time, host, Workstation, TargetUserName, accounts, eventcount
| sort - _time
| rename _time AS "Time", host AS "Host", Workstation AS "Source", TargetUserName AS "Target Username", accounts AS "Number of Accounts", eventcount AS "Total Attempts"
| convert ctime(Time)

This search detects non-existing account logon attempts via NTLM. Compared to S01D01, the field referencing the source is called Workstation since NTLM events provide a computer name instead of an IP address.

How to Implement: Credential Validation auditing setting must be enabled.

Known False Positives: Some applications may use outdated credentials configured for non-existing accounts.

S01D03 - Excessive failed password attempts from one source (Kerberos)

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4771 Status=0x18
| transaction IpAddress maxpause=5m maxevents=-1
| where eventcount > 5
| eval Source=if(IpAddress=="::1", Computer, IpAddress)
| eval accounts=mvcount(TargetUserName)
| where accounts > 2
| table _time, host, Source, TargetUserName, accounts, eventcount
| sort - _time
| rename _time AS "Time", host AS "Host", TargetUserName AS "Target Username", accounts AS "Number of Accounts", eventcount AS "Total Attempts"
| convert ctime(Time)

How to Implement: Kerberos Authentication Service auditing must be enabled.

S01D04 - Excessive failed password attempts from one source (NTLM)

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4776 Status=0xC000006A
| transaction Workstation maxpause=5m maxevents=-1
| where eventcount > 5
| eval accounts=mvcount(TargetUserName)
| where accounts > 2
| table _time, host, Workstation, TargetUserName, accounts, eventcount
| sort - _time
| rename _time AS "Time", host AS "Host", Workstation AS "Source", TargetUserName AS "Target Username", accounts AS "Number of Accounts", eventcount AS "Total Attempts"
| convert ctime(Time)

S01D05 - Password spraying (Kerberos)

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4771 Status=0x18
| bin _time span=15m
| eval Source=if(IpAddress=="::1", Computer, IpAddress)
| stats dc(TargetUserName) AS accounts values(TargetUserName) count by _time, Source
| where accounts > 5
| table _time, Source, accounts, TargetUserName, count
| sort - _time
| rename _time AS "Time", Source AS "Source", accounts AS "Unique Accounts", count AS "Total Attempts"
| convert ctime(Time)

How to Implement: Kerberos Authentication Service auditing must be enabled. The threshold for unique accounts needs to reflect the environment.

S01D06 - Password spraying (NTLM)

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4776 Status=0xC000006A
| bin _time span=15m
| stats dc(TargetUserName) AS accounts values(TargetUserName) count by _time, Workstation
| where accounts > 5
| table _time, Workstation, accounts, TargetUserName, count
| sort - _time
| rename _time AS "Time", Workstation AS "Source", accounts AS "Unique Accounts", count AS "Total Attempts"
| convert ctime(Time)

S01D07 - Multiple account lockouts from one source

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4740
| transaction TargetDomainName maxpause=5m maxevents=-1
| eval accounts=mvcount(TargetUserName)
| where accounts > 1
| table _time, host, TargetDomainName, TargetUserName, accounts, eventcount
| sort - _time
| rename _time AS "Time", host AS "Host", TargetDomainName AS "Source", TargetUserName AS "Target Username", accounts AS "Number of Accounts", eventcount AS "Total Events"
| convert ctime(Time)

This search detects several account lockouts made by a single source in a short time. User Account Management auditing setting enables logging for account lockout events (4740). Replacing the condition accounts > 1 by eventcount > 1 returns also repetitive lockout attempts for the same account.

S01D08 - Logon attempts towards disabled accounts (Kerberos)

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4768 Status=0x12
| transaction IpAddress maxpause=5m maxevents=-1
| where eventcount > 5
| eval Source=if(IpAddress=="::1", Computer, IpAddress)
| eval accounts=mvcount(TargetUserName)
| where accounts > 2
| table _time, host, Source, TargetUserName, accounts, eventcount
| sort - _time
| rename _time AS "Time", host AS "Host", TargetUserName AS "Target Username", accounts AS "Number of Accounts", eventcount AS "Total Attempts"
| convert ctime(Time)

This search identifies repetitive logon attempts with the use of disabled accounts. Event 4768 with status code 0x12 means the account is disabled. Note that event 4771 with status code 0x12 means that the account is locked (different meaning).

Task 2. Hunt for Kerberoasting

S02D01 - Possible Kerberoasting activity

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4769 (TicketEncryptionType=0x1 OR TicketEncryptionType=0x3 OR TicketEncryptionType=0x17 OR TicketEncryptionType=0x18)
| eval Source=if(IpAddress=="::1", Computer, IpAddress)
| table _time, host, Source, TargetUserName, ServiceName, TicketEncryptionType
| sort - _time
| rename _time AS "Time", host AS "Host", TargetUserName AS "Target Username", ServiceName AS "Service Name", TicketEncryptionType AS "Ticket Encryption"
| convert ctime(Time)

This search finds sources that requested service tickets with weak cipher suites. These encryption types should no longer be used by modern operating systems.

Known False Positives: Older operating systems or services that do not support AES encryption types.

S02D02 - Excessive service ticket requests from one source

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4769 ServiceName != krbtgt
| regex ServiceName != "\$$"
| transaction IpAddress maxpause=5m maxevents=-1
| eval services=mvcount(ServiceName)
| where services > 1
| eval Source=if(IpAddress=="::1", Computer, IpAddress)
| table _time, host, Source, TargetUserName, services, ServiceName, TicketEncryptionType
| sort - _time
| rename _time AS "Time", host AS "Host", TargetUserName AS "Target Username", services AS "Number of Services", ServiceName AS "Service Name", TicketEncryptionType AS "Ticket Encryption"
| convert ctime(Time)

Requests for several different service names within a short time from a single account are suspicious. Service ticket requests for krbtgt service and computer account service names (ending with $) are filtered out.

S02D03 - Suspicious external service ticket requests

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4769 IpPort > 0 (IpPort < 1024 OR (NOT (IpAddress=10.0.0.0/8 OR IpAddress=172.16.0.0/12 OR IpAddress=192.168.0.0/16 OR IpAddress=127.0.0.1 OR IpAddress=::1)))
| table _time, host, IpAddress, IpPort, TargetUserName, ServiceName, TicketEncryptionType
| sort - _time
| rename _time AS "Time", host AS "Host", IpAddress AS Source, IpPort AS "Source Port", TargetUserName AS "Target Username", ServiceName AS "Service Name", TicketEncryptionType AS "Ticket Encryption"
| convert ctime(Time)

This search tracks service requests by examining the IP address and port number. Port values under 1024 and any non-private IP addresses are those of interest.

S02D04 - Detecting Kerberoasting with a honeypot

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4769 ServiceName=Honeypot01
| eval Source=if(IpAddress=="::1", Computer, IpAddress)
| table _time, host, Source, TargetUserName, ServiceName, TicketEncryptionType
| sort - _time
| rename _time AS "Time", host AS "Host", TargetUserName AS "Target Username", ServiceName AS "Service Name", TicketEncryptionType AS "Ticket Encryption"
| convert ctime(Time)

Uses a fake service account (honeypot) that is never really used in the environment. A service ticket request for this account is only made by an adversary. A fake account needs a fake SPN registered. Set AdminCount attribute to 1 to raise attractivity.

S02D05 - Detecting Kerberoasting via PowerShell

index="ad_hunting" source="WinEventLog:Microsoft-Windows-PowerShell/Operational" (EventCode=4103 OR EventCode=4104)
| transaction Computer maxpause=15m maxevents=-1
| eval raw=_raw
| search [| inputlookup service_accounts.csv
| eval raw="*" . account . "*"
| fields raw]
| where eventcount > 2
| table _time, Computer, eventcount
| sort - _time
| rename _time AS "Time", Computer AS "Host", eventcount AS "Number of Events"
| convert ctime(Time)

This search detects attempts to manipulate service accounts via PowerShell. It looks for the occurrence of any service account in the raw data of PowerShell events using a lookup file.

Task 3. Hunt for Credential Dumping

S03D01 - Possible dump of lsass.exe (Sysmon events)

index="ad_hunting" source="xmlwineventlog:microsoft-windows-sysmon/operational" EventCode=8 OR EventCode=10 NOT GrantedAccess=0x1400 NOT GrantedAccess=0x1000 NOT GrantedAccess=0x100000
| where (TargetImage LIKE "%lsass.exe")
|search NOT SourceImage="C:\\Windows\\system32\\wininit.exe" NOT SourceImage="C:\\Windows\\system32\\csrss.exe"
| transaction host, SourceImage, SourceProcessId maxspan=15m
| table _time, host, SourceImage, SourceProcessId, GrantedAccess
| sort - _time
| rename _time AS "Time", host AS "Host", SourceImage AS "Process", SourceProcessId AS "Process ID", GrantedAccess AS "Access Mask"
| convert ctime(Time)

Detects possible dump of the LSASS process memory via Sysmon events. Focuses on Sysmon events with event codes 10 (ProcessAccess) and 8 (CreateRemoteThread). Whitelists low-privileged access masks and legitimate processes like wininit.exe.

How to Implement: Sysmon must be deployed and configured to log process accesses (event 10) and optionally creation of remote threads (event 8).

S03D02 - Possible dump of lsass.exe (Windows events)

index="ad_hunting" source=XmlWinEventLog:Security EventCode=4656 NOT AccessMask=0x1400 NOT AccessMask=0x1000 NOT AccessMask=0x100000
| where (ObjectName LIKE "%lsass.exe")
| search NOT ProcessName="C:\\Windows\\system32\\lsass.exe"
| transaction host, ProcessName, ProcessId maxspan=15m
| table _time, host, ProcessName, ProcessId, AccessMask
| sort - _time
| rename _time AS "Time", host AS "Host", ProcessName AS "Process", ProcessId AS "Process ID", AccessMask AS "Access Mask"
| convert ctime(Time)

Uses event 4656 (A handle to an object was requested). For event 4656 to be logged, auditing settings under Object Access category must be enabled.

S03D03 - Creation of a dump file

index="ad_hunting" source="xmlwineventlog:microsoft-windows-sysmon/operational" EventCode=11 TargetFilename=*dmp
| table _time, host, Image, ProcessId, TargetFilename
| sort - _time
| rename _time AS "Time", host AS "Host", Image AS "Process", ProcessId AS "Process ID", TargetFilename AS "Filename"
| convert ctime(Time)

Hunts for the creation of .dmp files using Sysmon event 11 (FileCreate).

S03D04 - Installation of an unsigned driver

index="ad_hunting" source="xmlwineventlog:microsoft-windows-sysmon/operational" EventCode=6 Signed=false
| table _time, host, ImageLoaded, SHA1, SignatureStatus
| sort - _time
| rename _time AS "Time", host AS "Host", ImageLoaded AS "Image Loaded", SignatureStatus AS "Signature Status"
| convert ctime(Time)

Some tools used for credential dumping, such as Mimikatz, may attempt to install their own driver. Looks for Sysmon event 6 (Driver loaded) with Signed equal to false.

S03D05 - Access to GPP honeypot in SYSVOL

index="ad_hunting" source=XmlWinEventLog:Security EventCode=5145 RelativeTargetName="*test.local\\Policies\\{12345}*"
| transaction IpAddress, SubjectUserSid maxspan=5m maxevents=-1
| table _time, host, IpAddress, SubjectUserName, SubjectUserSid, SubjectLogonId
| sort - _time
| rename _time AS "Time", host AS "Host", IpAddress AS "Source IP", SubjectUserName AS "Username", SubjectUserSid AS "Account SID", SubjectLogonId AS "Logon ID"
| convert ctime(Time)

Uses Windows event 5145 to detect access to a fake GPP honeypot in SYSVOL. A fake group policy needs to be created with deny permissions set for Everyone.

S03D06 - Possible credential database dumping

index="ad_hunting" source="xmlwineventlog:security"
EventCode=4688
NewProcessName="*vssadmin.exe"

Detects usage of vssadmin.exe, ntdsutil.exe, wmic.exe, or reg.exe to access NTDS.dit by looking at command line parameters of newly created processes.

S03D07 - Possible dumping via DC synchronization

index="ad_hunting"
source="xmlwineventlog:security"
EventCode=4662
Properties="*1131f6aa-9c07-11d1-f79f-00c04fc2dcd2*" OR Properties="*1131f6ad-9c07-11d1-f79f-00c04fc2dcd2*" OR Properties="*1131f6ab-9c07-11d1-f79f-00c04fc2dcd2*"
Caller_User_Name=Administrator

Detects DCSync by focusing on event 4662 (An operation was performed on an object). The Properties field filters for DC replication access rights. The Logon ID should be correlated with event 4624 to determine the source workstation.

index="ad_hunting" EventCode=4624 0x107d67

The identified IP addresses from the correlation should be those of Domain Controllers; otherwise we are dealing with a compromised endpoint impersonating a Domain Controller.