Skip to content

abaveja/MDI-detections

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 

Repository files navigation

Tips & Tricks #Investigate with Microsoft Defender for Identity


Author: Daniel Pasquier

Introduction

Microsoft Defender for Identity (MDI) is the ideal solution for detecting and investigating advanced threats, compromised identities, and malicious insider actions in Active Directory. For each AD Cybersecurity Crisis where our Microsoft Detection and Response Team (DART) is involved, they always ask for installing MDI to better investigate and set the appropriate remediation actions. SecOp analysts and security professionals who use Microsoft Defender for Identity give us great feedbacks (such as preventing CryptoLocker activities etc..); and Jugoslav and I would like to share best practices from the field. Keep in mind that MDI has unique capabilities to capture source data using deep packet inspection (traffic on all DCs), Event Tracing, Event Logs… combined with User profiling, Machine learning and fast updated Alerts based on the Threat landscape.

MDI is also very relevant when the source attack comes from an unknown, unmanaged machine (no AV/EDR/GPO) where you have no control...

Here are the MDI capabilities :

In this documentation, we want to share some of useful Advanced Hunting KQL queries that you can use with the Microsoft 365 Defender portal available from https://security.microsoft.com.

To get the list of ActionType you can use in your environment for IdentityDirectoryEvents API, just run:

IdentityDirectoryEvents
| summarize by ActionType

Here are few tips you can use or optimize:

Tips 1 – List of machines where service account is running

In case of a SVC account is compromised, or if you need to change the SVC account password or if someone knowing the SVC account password leaves your organisation; it could be very useful to know where a service account is configured (consider using gMSA instead):

IdentityLogonEvents
| where Application == @"Active Directory"
| where AccountUpn == @"[email protected]" // set your SVC account here
| where ActionType == @"LogonSuccess"
| summarize count() by DeviceName

In this example, over the last 30 days, [email protected] is used on 432 machines (logon success), the list of machines can be exported into a CSV file. If needed, you can add additional information such as the LogonType (Resource access, Credentials validation…). This is not an exhaustive list as the ActionType == LogonSuccess must occur in the last month, but after several months you should catch a good list.

Image1

Tips 2 – Kerberos versus NTLM use

We all know that Kerberos provides several security benefits over NTLM and provides best performance. Here the following KQL query will provide the ratio of the success logon using NTLM and Kerberos:

IdentityLogonEvents
| where ActionType == "LogonSuccess"
| where Application == "Active Directory"
| where Protocol in ("NTLM", "Kerberos")
| summarize count() by Protocol

Image2

Since the NTLMv1 hash is always at the same length, it is only a matter of seconds if an attacker wants to crack it. In addition, the challenge-response mechanism exposes the password to offline cracking. It is recommended not to use it if possible.

To track the use of NTLMv1 you can run:

IdentityLogonEvents
| where Timestamp > ago (7d) // shows activies in the last 7 days
| where Application contains "directory"
| where Protocol == "NTLM"
| extend AddData = todynamic(AdditionalFields)
| extend NTLMV1 = tostring(AddData.IsNtlmV1)
| extend Account = tostring((AddData).["ACTOR.ACCOUNT"])
| where NTLMV1 == "True"
| summarize count() by Account, AccountSid , DC = DestinationDeviceName

To understand failure reasons during Kerberos authentication in your environment:

IdentityLogonEvents
| where ActionType == "LogonFailed"
| where Application == "Active Directory"
| where Protocol == "Kerberos"
| summarize count() by FailureReason

Image3

Do you want to know who and from where weak cipher such as DES or RC4 are used for Kerberos authentication? Just use the following query:

IdentityLogonEvents
| where Protocol == @"Kerberos"
| extend ParsedFields=parse_json(AdditionalFields)
| project Timestamp, ActionType, DeviceName, IPAddress, DestinationDeviceName, AccountName, AccountDomain, EncryptionType = tostring(ParsedFields.EncryptionType)
| where EncryptionType == @"Rc4Hmac"

Image15

Remark : This information is also available from the Microsoft Defender for Identity's identity security posture assessments

Tips 3 – List of files copied from a client to DCs over the last 30 days

Except if your DCs are used as files server, which is of course not recommended at all you should not see many files copied from a workstation or member server to DCs.

Using this KQL query you can monitor this activity and identify potential suspect activities or even risky activities:

IdentityDirectoryEvents
| where ActionType == @"SMB file copy"
| extend ParsedFields=parse_json(AdditionalFields)
| project Timestamp, ActionType, DeviceName, IPAddress, AccountDisplayName, DestinationDeviceName, DestinationPort, FileName=tostring(ParsedFields.FileName), FilePath=tostring(ParsedFields.FilePath), Method=tostring(ParsedFields.Method)
| where Method == @"Write"

Image4

Remark: MDI has also an alert for data exfiltration (such as the NTDS.DIT file copied from a DC to a workstation).

Tips 4 – “Account Password Not Required” changed from FALSE to TRUE

Even with a password policy in place that affects all user accounts, it is possible to set a blank password for a user with the setting “Account Password Not Required” using for example a PowerShell cmdlet (not possible through the GUI). This is why it's important to list all users with this setting enabled using the MDI portal but also to track all changes from “Account Password Not Required” = FALSE to TRUE:

IdentityDirectoryEvents
| where ActionType == @"Account Password Not Required changed"
| extend PreviousValue = todynamic(AdditionalFields)["FROM Account Password Not Required"]
| extend NewValue = todynamic(AdditionalFields)["TO Account Password Not Required"]
| where "True"==NewValue
| project Timestamp, ActionType, Application, TargetAccountDisplayName, PreviousValue, NewValue

Image5

Tips 5 – “Account Password Never Expires” changed from FALSE to TRUE

This setting could be expected for service account if you can’t use gMSA; however, we should never see “Account Password Never Expires” changed from FALSE to TRUE for an user account (not SVC) or for an admin account (a lazy one 😊). Here how to track this information:

IdentityDirectoryEvents
| where ActionType == @"Account Password Never Expires changed"
| extend PreviousValue = todynamic(AdditionalFields)["FROM Account Password Never Expires"]
| extend NewValue = todynamic(AdditionalFields)["TO Account Password Never Expires"]
| where "True"==NewValue
| where TargetAccountDisplayName !contains "SVC"
| project Timestamp, ActionType, Application, TargetAccountDisplayName, PreviousValue, NewValue

Image6

Tips 6 – Expected “PowerShell execution” on DCs?

MDI generates an alert when remote code execution is performed against a DC, however if you need further investigation, you can run the following query to get the list of PowerShell command executed remotely to a DC (Of course it’s the same logic for “WMI execution”, PSEXE execution…):

IdentityDirectoryEvents
| where ActionType == @"PowerShell execution"
| extend Command = todynamic(AdditionalFields)["PowerShell execution"]
| project Timestamp, ActionType, DeviceName, IPAddress, DestinationDeviceName, AccountName, AccountDomain, Command

Image7

Tips 7 – Expected “Service creation” on DCs?

Do you want to know which new service, task scheduled are created on yours DCs remotely? Here is a sample for all services except for two which are expected in my environment:

IdentityDirectoryEvents
| where ActionType == @"Service creation"
| extend ParsedFields=parse_json(AdditionalFields)
| project Timestamp, ActionType, TargetDeviceName, AccountName, AccountDomain, ServiceName=tostring(ParsedFields.ServiceName), ServiceCommand=tostring(ParsedFields.ServiceCommand)
| where ServiceName != @"Microsoft Monitoring Agent Azure VM Extension Heartbeat Service"
| where ServiceName != @"MOMAgentInstaller"
| where ServiceName !contains @"MpKsl"

Image8

Tips 8 – Total Count – Computers with failed logon unknown users (>100)

This query provides information mainly for misconfigured application that generate failed logon with status “UnknownUser”, probably because the wrong name was set.

Of course, it could be also an attacker looking for valid account name based on the DC response WrongPassword (0xc000006a) or NoSuchUser (0xc0000064):

IdentityLogonEvents
| where LogonType == "Failed logon"
| where FailureReason == "UnknownUser"
| where isnotempty(DestinationDeviceName)
| summarize Attempts = count() by DeviceName, DestinationDeviceName , FailureReason
| where Attempts > 100

Image9

Tips 9 – Top Spike for user’s logon activities over the last 30 days

If you see a logon spike activity based on the activity during the past 30 days it could worth an investigation 😊 :

let interval = 12h;
IdentityLogonEvents
| where isnotempty(AccountUpn)
| make-series LogonCount = count() on Timestamp from ago(30d) to now() step interval by AccountUpn
| extend (flag, score, baseline) = series_decompose_anomalies(LogonCount)
| mv-expand with_itemindex = FlagIndex flag to typeof(int) // Expand, but this time include the index in the array as FlagIndex
| where flag == 1 // Once again, filter only to spikes
| extend SpikeScore = todouble(score[FlagIndex]) // This will get the specific score associated with the detected spike
| summarize MaxScore = max(SpikeScore) by AccountUpn
| top 5 by MaxScore desc
| join kind=rightsemi IdentityLogonEvents on AccountUpn
| summarize count() by AccountUpn, bin(Timestamp, interval)
| render timechart

Image10

Tips 10 – Processes that performed LDAP authentication with cleartext passwords

This query is available from https://github.com/Iveco/xknow_infosec/blob/main/M365D_tables.md and requires to have Microsoft Defender for Endpoint (MDE) to combine the result with MDI detection :

IdentityLogonEvents
| where Timestamp > ago(7d)
| where LogonType == "LDAP cleartext" //and isnotempty(AccountName)
| project LogonTime = Timestamp, DeviceName, Application, ActionType, LogonType //,AccountName
| join kind=inner (
DeviceNetworkEvents
| where Timestamp > ago(7d) | where ActionType == "ConnectionSuccess"
| extend DeviceName = toupper(trim(@"..$",DeviceName))

| where RemotePort == "389"
| project NetworkConnectionTime = Timestamp, DeviceName, AccountName = InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine ) on DeviceName
| where LogonTime - NetworkConnectionTime between (-2m .. 2m)
| project Application, LogonType, ActionType, LogonTime, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine //, AccountName

Tips 11 – Detect T0 Admin login on unsecure machines

We all know that T0 Admins should be used only on secured/protected access workstations (SAW/PAW) to mitigate credential theft and a GPO can be used to deny logon types on all machines except a whitelist that matches SAW or PAW machines. However, you can query for “Potential lateral movement path identified” and exclude the machines they should only be logging in from assuming when the sensitive account logged on it would create a LMP. So let’s say you have a Tier0 machine that the admins should be logon from you can create a query like this:

let T0Machine = "adminpc.contoso.azure ";
IdentityDirectoryEvents
| where ActionType == "Potential lateral movement path identified"
| where AccountUpn == @"[email protected]"
| where DeviceName <> T0Machine

Tips 12 – Identify machines or IPs from where Account Lockout threshold is triggered

The account lockout policy is a built-in security measure that limits malicious users and hackers from illegitimately accessing your network resources. However, employees often use multiple devices, numerous productivity applications, Windows services, tasks, network mapping and more, which can store a wrong password and set off the account lockout.
It could be interesting to identify machines or IPs from where Account Lockout threshold is triggered only based on MDI raw data.
Remark: DeviceName and IPAdress can sometime be empty (no raw data).

IdentityLogonEvents
| where Application == @"Active Directory" // AD only
| where AccountDomain == @"msdemo.org" // if needed to filter by domain
| where ActionType == @"LogonFailed"
| where FailureReason == @"WrongPassword" or FailureReason == @"AccountLocked" //badpasswordcount attribute
| summarize FailureReason = count() by DeviceName, IPAddress, AccountUpn
| where FailureReason > 15 //depending on the Account Lockout threshold

test

Tips 13 – Create a detection / notification rule

Depending on the columns result you can set a detection rule to run at regular intervals, generating alerts and taking response actions whenever there are matches; this could be useful to notify your SOC team.

See Create and manage custom detection rules in Microsoft 365 Defender

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published