Sentinel - Analytic template - MFA Rejected by User

Brass Contributor

Hi, we are having a few issues with the Sentinel templated analytic rule - MFA Rejected by User (version 2.0.3) - https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft%20Entra%20ID/Analytic%20Rule...

 

Over the last 30 days this analytic rule has generated 98 incidents which are all false positives.

 

The analytic rule works on looking at Entra ID signinlogs against result type 500121 with one or more of the following additional details reported "MFA denied; user declined the authentication" or "fraud".

 

It maps UEBA identity information then join the behavior analytics data summarised by IP Address. It's the summarising of the IP address data which has me questioning the code.

 

When we get an event in the signin logs it also generates an event in the UEBA behavior analytic table along with a IP investigation score. If you have multiple events in the time period of the rules query period then the summarizing does a SUM() against the IP investigation data which can turn into a high which breaches the threshold.

 

The default threshold is 20 but I have seen IP investigation scores summed again being between 60 and 100+ but the individual event record for the MFA rejection gives a score of 3 or 4.

 

Anyone an expert with UEBA and KQL be able to tell me if the original code looks ok? - https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft%20Entra%20ID/Analytic%20Rule...

 

Would to be better served by the following code?

 

let riskScoreCutoff = 20; //Adjust this based on volume of results
SigninLogs
| where ResultType == 500121
| extend additionalDetails_ = tostring(Status.additionalDetails)
| extend UserPrincipalName = tolower(UserPrincipalName)
| where additionalDetails_ =~ "MFA denied; user declined the authentication" or additionalDetails_ has "fraud"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserId = any(UserId), AADTenantId=any(AADTenantId), DeviceName=any(DeviceDetail.displayName), IsManaged=any(DeviceDetail.isManaged), OS = any(DeviceDetail.operatingSystem) by UserPrincipalName, IPAddress, AppDisplayName
| extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
| 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 = tostring(InvestigationPriority)
by IPAddress, IPInvestigationPriority=InvestigationPriority)
on IPAddress
| extend UEBARiskScore = IPInvestigationPriority
| where UEBARiskScore > riskScoreCutoff
| sort by UEBARiskScore desc

 

 

0 Replies