-
Notifications
You must be signed in to change notification settings - Fork 3.1k
/
MFAUserBlocked.yaml
103 lines (101 loc) · 4.84 KB
/
MFAUserBlocked.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
id: 75fd68a2-9ed4-4a1c-8bd7-18efe4c99081
name: Login attempt by Blocked MFA user
description: |
'An account could be blocked if there are too many failed authentication attempts in a row. This hunting query identifies if a MFA user account that is set to blocked tries to login to Microsoft Entra ID.
This query has also been updated to include UEBA logs IdentityInfo and BehaviorAnalytics for contextual information around the results.'
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: BehaviorAnalytics
dataTypes:
- BehaviorAnalytics
- connectorId: BehaviorAnalytics
dataTypes:
- IdentityInfo
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
let riskScoreCutoff = 20; //Adjust this based on volume of results
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let lookback = starttime - 7d;
let isGUID = "[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}";
let MFABlocked = SigninLogs
| where TimeGenerated between(starttime..endtime)
| where ResultType != "0"
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails), Status = strcat(ResultType, ": ", ResultDescription)
| where StatusDetails =~ "MFA denied; user is blocked"
| extend Unresolved = iff(Identity matches regex isGUID, true, false);
// Lookup up resolved identities from last 7 days
let identityLookup = SigninLogs
| where TimeGenerated between(lookback..starttime)
| where not(Identity matches regex isGUID)
| summarize by UserId, lu_UserDisplayName = UserDisplayName, lu_UserPrincipalName = UserPrincipalName;
// Join resolved names to unresolved list from MFABlocked signins
let unresolvedNames = MFABlocked | where Unresolved == true | join kind= inner (
identityLookup
) on UserId
| extend UserDisplayName = lu_UserDisplayName, UserPrincipalName = lu_UserPrincipalName
| project-away lu_UserDisplayName, lu_UserPrincipalName;
// Join Signins that had resolved names with list of unresolved that now have a resolved name
let u_MFABlocked = MFABlocked | where Unresolved == false | union unresolvedNames;
u_MFABlocked
| extend OS = tostring(DeviceDetail.operatingSystem), Browser = tostring(DeviceDetail.browser)
| extend FullLocation = strcat(Location,'|', LocationDetails.state, '|', LocationDetails.city)
| summarize TimeGenerated = make_list(TimeGenerated), Status = make_list(Status), IPAddresses = make_list(IPAddress), IPAddressCount = dcount(IPAddress),
AttemptCount = count() by UserPrincipalName, UserId, UserDisplayName, AppDisplayName, Browser, OS, FullLocation , CorrelationId
| mvexpand TimeGenerated, IPAddresses, Status
| extend TimeGenerated = todatetime(tostring(TimeGenerated)), IPAddress = tostring(IPAddresses), Status = tostring(Status)
| project-away IPAddresses
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserPrincipalName, UserId, UserDisplayName, Status, IPAddress, IPAddressCount, AppDisplayName, Browser, OS, FullLocation
| extend timestamp = StartTime, UserPrincipalName = tolower(UserPrincipalName), Account_0_Name = UserPrincipalName, IP_0_Address = IPAddress
| join kind=leftouter (
IdentityInfo
| summarize LatestReportTime = arg_max(TimeGenerated, *) by AccountUPN
| project AccountUPN, Tags, JobTitle, GroupMembership, AssignedRoles, UserType, IsAccountEnabled
| summarize
Tags = make_set(Tags, 1000),
GroupMembership = make_set(GroupMembership, 1000),
AssignedRoles = make_set(AssignedRoles, 1000),
UserType = make_set(UserType, 1000),
UserAccountControl = make_set(UserType, 1000)
by AccountUPN
| extend UserPrincipalName=tolower(AccountUPN)
) on UserPrincipalName
| join kind=leftouter (
BehaviorAnalytics
| where ActivityType in ("FailedLogOn", "LogOn")
| where isnotempty(SourceIPAddress)
| project UsersInsights, DevicesInsights, ActivityInsights, InvestigationPriority, SourceIPAddress
| project-rename IPAddress = SourceIPAddress
| summarize
UsersInsights = make_set(UsersInsights, 1000),
DevicesInsights = make_set(DevicesInsights, 1000),
IPInvestigationPriority = sum(InvestigationPriority)
by IPAddress
) on IPAddress
| extend UEBARiskScore = IPInvestigationPriority
| where UEBARiskScore > riskScoreCutoff
| sort by UEBARiskScore desc
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: UserPrincipalName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 2.0.2
metadata:
source:
kind: Community
author:
name: Ajeet Prakash
support:
tier: Community
categories:
domains: [ "Security - Other", "Identity" ]