Zum Hauptinhalt springen

Authentication

These queries are related to authentication.

Both tables SigninLogs and AADNonInteractiveUserSignInLogs are related to authentication.

UserId is a hash like "6829200b-9ed3-403b-9e34-51221a4d9af7"

Login check

union isfuzzy=true SigninLogs, AADNonInteractiveUserSignInLogs
// Rename all columns named _dynamic to normalize the column names
| extend ConditionalAccessPolicies = iff(isempty( ConditionalAccessPolicies_dynamic ), todynamic(ConditionalAccessPolicies_string), ConditionalAccessPolicies_dynamic)
| extend Status = iff(isempty( Status_dynamic ), todynamic(Status_string), Status_dynamic)
| extend MfaDetail = iff(isempty( MfaDetail_dynamic ), todynamic(MfaDetail_string), MfaDetail_dynamic)
| extend DeviceDetail = iff(isempty( DeviceDetail_dynamic ), todynamic(DeviceDetail_string), DeviceDetail_dynamic)
| extend LocationDetails = iff(isempty( LocationDetails_dynamic ), todynamic(LocationDetails_string), LocationDetails_dynamic)
// Remove duplicated columns
| project-away *_dynamic, *_string
| where IPAddress in ("", "")
| where UserId == "" or UserPrincipalName == ""
| where Type == "SigninLogs"
//| where TimeGenerated between( .. now())
| where TimeGenerated > ago(2d)
| summarize arg_max(Type, UserDisplayName, IPAddress, DeviceDetail, ConditionalAccessStatus, ResultType, ResultDescription, ClientAppUsed, UserAgent, AppDisplayName, LocationDetails, AuthenticationDetails, MfaDetail, *) by TimeGenerated
| order by TimeGenerated asc
// Summarize by status
//| summarize count()by Type, IPAddress, ConditionalAccessStatus, ResultType, ResultDescription, tostring(DeviceDetail)

Notes

  • The 50076 error code is not caused by explicit user MFA denial.
  • The 500121 error code represents a failed attempt to authenticate using a second factor. Specifically, the analytic triggers when more than 10 MFA user prompts fail within 10 minutes.

For LDAP, we can use

IdentityLogonEvents
| where AccountName has ""
// | where DeviceName has_any ("")
| summarize arg_max(ActionType, LogonType, FailureReason, IPAddress, Application, Protocol, *) by TimeGenerated
| order by TimeGenerated asc
//
// Summary by status and count
// | summarize arg_max(DeviceName, ActionType, LogonType, FailureReason, IPAddress, *) by FailureReason
// | summarize count()by DeviceName, ActionType, LogonType, FailureReason, IPAddress, Protocol
| project-reorder TimeGenerated, Protocol, ActionType, FailureReason, DeviceName, IPAddress, DestinationDeviceName, DestinationIPAddress, AdditionalFields
// To see errors and successes from NTLM and Kerberos...
SecurityEvent
| where Account has ""
| where EventID == 4624 or EventID == 4625
| extend Outcome = iff(EventID == 4624, "Success", "Failure")
// | where Outcome <> "Failure"
| order by TimeGenerated asc
| summarize count() by Outcome, EventID, FailureReason, Computer, AuthenticationPackageName, Activity

FailureReason %%2313 means - Unknown user name or bad password (529).

LDAP queries

IdentityQueryEvents
| where ActionType has "LDAP query" and IPAddress == ""
| where TimeGenerated between (todatetime('2024-01-04T13:23:53.4327111Z') .. todatetime('2024-01-04T13:40:08.3519801Z'))
| project-reorder TimeGenerated, DeviceName, IPAddress, DestinationDeviceName, TargetAccountUpn

AD account

For cases like "AD user enabled and password not set within 48 hours"

Useful search

search "accountName" and "HOST"
| where $table !in ("DeviceEvents", "Event", "SecurityEvent")
| where TimeGenerated between(datetime("2023-02-03 00:00:00") .. now())
| order by TimeGenerated asc

Multiple auth attemp followed by a success

SecurityEvent - Multiple authentication failures followed by a success

SecurityEvent
| where Account has ""
| where EventID == 4624 or EventID == 4625
| extend Outcome = iff(EventID == 4624, "Success", "Failure")
// | where Outcome <> "Failure"
| order by TimeGenerated asc

ServicePrincipal resource access

To see if a SP accessed a resource in a given time frame, grouping the count by day.

AADManagedIdentitySignInLogs
| where ServicePrincipalId == ""
| where ResourceIdentity == ""
| where TimeGenerated > ago(15d)
| summarize count() by bin(TimeGenerated, 15d)

Service Principal SignIn

AADServicePrincipalSignInLogs
| where ServicePrincipalName == ""
| order by TimeGenerated asc

Password Reset

Use the following query to check the password reset process.

AuditLogs
| extend ib=parse_json(InitiatedBy)
| where InitiatedBy contains "User.Name"
| project TimeGenerated, Result, ResultDescription, ib['user']['ipAddress']
| order by TimeGenerated asc

Then, you can see if the user successfully went through every step.

PR on Machine Accounts

SecurityEvent
//| where TimeGenerated between(( -1h) .. ( +1h))
| where EventID in ("4723", "4724", "4738")
| where Account in ("CORP\\MUCMSX03$", "CORP\\MUCMSX05$")
| where TargetAccount == "AD\\who"
| where Computer has_any("MUCDC01", "MUCDC02", "MUCMSX05")
| order by TimeGenerated asc
| project-reorder TimeGenerated, Activity, Account, TargetAccount, SubjectAccount, Computer, EventData

PR data sources

let action = dynamic(["change", "changed", "reset"]);
let pWord = dynamic(["password", "credentials"]);
let PasswordResetMultiDataSource =
(union isfuzzy=true
(//Password reset events
//4723: An attempt was made to change an account's password
//4724: An attempt was made to reset an accounts password
SecurityEvent
| where EventID in ("4723", "4724")
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName),
(//Azure Active Directory Password reset events
AuditLogs
| where OperationName has_any (pWord)
and OperationName has_any (action)
and Result =~ "success"
| extend
AccountType = tostring(TargetResources[0].type),
Account = tostring(TargetResources[0].userPrincipalName),
TargetUserName = tolower(tostring(TargetResources[0].displayName))
| project TimeGenerated, AccountType, Account, Computer = "", Type),
(//OfficeActive ActiveDirectory Password reset events
OfficeActivity
| where OfficeWorkload == "AzureActiveDirectory"
| where (ExtendedProperties has_any (pWord) or ModifiedProperties has_any (pWord)) and (ExtendedProperties has_any (action) or ModifiedProperties has_any (action))
| extend AccountType = UserType, Account = OfficeObjectId
| project TimeGenerated, AccountType, Account, Type, Computer = ""),
(// Unix syslog password reset events
Syslog
| where Facility in ("auth", "authpriv")
| where SyslogMessage has_any (pWord) and SyslogMessage has_any (action)
| extend AccountType = iif(SyslogMessage contains "root", "Root", "Non-Root")
| where SyslogMessage matches regex ".*password changed for.*"
| parse SyslogMessage with * "password changed for" Account
| project TimeGenerated, AccountType, Account, Computer = HostName, Type)
);

Account enumeration

An actor on DVCEXP0001 performed suspicious account enumeration, exposing 6 existing account names.

Then we'll search the logins by the device.

IdentityLogonEvents
| where DeviceName has "DVCEXP0001"
//| summarize count()by AccountName, FailureReason, ActionType, IPAddress
| project-reorder TimeGenerated, DeviceName, AccountDomain, AccountName, FailureReason, ActionType, IPAddress