MFA Rejected by User
Id | d99cf5c3-d660-436c-895b-8a8f8448da23 |
Rulename | MFA Rejected by User |
Description | Identifies occurances where a user has rejected an MFA prompt. This could be an indicator that a threat actor has compromised the username and password of this user account and is using it to try and log into the account. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins This query has also been updated to include UEBA logs IdentityInfo and BehaviorAnalytics for contextual information around the results. Please note, MFA Failed logons from known IP ranges can be benign depending on the conditional access policies. In case of noisy behavior, consider tuning the source IP ranges or location filter after careful consideration |
Severity | Medium |
Tactics | InitialAccess |
Techniques | T1078.004 |
Required data connectors | AzureActiveDirectory BehaviorAnalytics |
Kind | Scheduled |
Query frequency | 1h |
Query period | 1h |
Trigger threshold | 0 |
Trigger operator | gt |
Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/MFARejectedbyUser.yaml |
Version | 2.0.4 |
Arm template | d99cf5c3-d660-436c-895b-8a8f8448da23.json |
let riskScoreCutoff = 3; //Adjust this score threshold based on volume of results. Activities identified as the most abnormal receive the highest scores (on a scale of 0-10)
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) by UserPrincipalName, UserId, AADTenantId, FailedIPAddress = IPAddress
| 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
//Below it will be joined with BehaviorAnalytics table to the Failed IP Addresses
| join kind=leftouter (
BehaviorAnalytics
| where ActivityType in ("FailedLogOn", "LogOn")
| where isnotempty(SourceIPAddress)
| project UsersInsights, DevicesInsights, ActivityInsights, InvestigationPriority, SourceIPAddress, UserName
| project-rename FailedIPAddress = SourceIPAddress, Name = UserName
| summarize
MaxInvestigationScore = max(InvestigationPriority) // Only retrieve maximum Investigation Property score for both FailedIP and User
by FailedIPAddress, Name)
on FailedIPAddress, Name // Joining on both IP and User so as to only return context associated with same user
| extend UEBARiskScore = MaxInvestigationScore
| project-away *1 // removing duplicate columns post outer join from output
| where UEBARiskScore > riskScoreCutoff
| sort by UEBARiskScore desc
relevantTechniques:
- T1078.004
triggerThreshold: 0
status: Available
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/MFARejectedbyUser.yaml
version: 2.0.4
requiredDataConnectors:
- dataTypes:
- SigninLogs
connectorId: AzureActiveDirectory
- dataTypes:
- BehaviorAnalytics
connectorId: BehaviorAnalytics
- dataTypes:
- IdentityInfo
connectorId: BehaviorAnalytics
query: |
let riskScoreCutoff = 3; //Adjust this score threshold based on volume of results. Activities identified as the most abnormal receive the highest scores (on a scale of 0-10)
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) by UserPrincipalName, UserId, AADTenantId, FailedIPAddress = IPAddress
| 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
//Below it will be joined with BehaviorAnalytics table to the Failed IP Addresses
| join kind=leftouter (
BehaviorAnalytics
| where ActivityType in ("FailedLogOn", "LogOn")
| where isnotempty(SourceIPAddress)
| project UsersInsights, DevicesInsights, ActivityInsights, InvestigationPriority, SourceIPAddress, UserName
| project-rename FailedIPAddress = SourceIPAddress, Name = UserName
| summarize
MaxInvestigationScore = max(InvestigationPriority) // Only retrieve maximum Investigation Property score for both FailedIP and User
by FailedIPAddress, Name)
on FailedIPAddress, Name // Joining on both IP and User so as to only return context associated with same user
| extend UEBARiskScore = MaxInvestigationScore
| project-away *1 // removing duplicate columns post outer join from output
| where UEBARiskScore > riskScoreCutoff
| sort by UEBARiskScore desc
name: MFA Rejected by User
kind: Scheduled
id: d99cf5c3-d660-436c-895b-8a8f8448da23
entityMappings:
- fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
entityType: Account
- fieldMappings:
- identifier: AadUserId
columnName: UserId
entityType: Account
- fieldMappings:
- identifier: Address
columnName: FailedIPAddress
entityType: IP
triggerOperator: gt
description: |
'Identifies occurances where a user has rejected an MFA prompt. This could be an indicator that a threat actor has compromised the username and password of this user account and is using it to try and log into the account.
Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins
This query has also been updated to include UEBA logs IdentityInfo and BehaviorAnalytics for contextual information around the results.
Please note, MFA Failed logons from known IP ranges can be benign depending on the conditional access policies. In case of noisy behavior, consider tuning the source IP ranges or location filter after careful consideration'
tactics:
- InitialAccess
queryFrequency: 1h
severity: Medium
tags:
- AADSecOpsGuide
queryPeriod: 1h
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"workspace": {
"type": "String"
}
},
"resources": [
{
"apiVersion": "2024-01-01-preview",
"id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/d99cf5c3-d660-436c-895b-8a8f8448da23')]",
"kind": "Scheduled",
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/d99cf5c3-d660-436c-895b-8a8f8448da23')]",
"properties": {
"alertRuleTemplateName": "d99cf5c3-d660-436c-895b-8a8f8448da23",
"customDetails": null,
"description": "'Identifies occurances where a user has rejected an MFA prompt. This could be an indicator that a threat actor has compromised the username and password of this user account and is using it to try and log into the account.\nRef : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins\nThis query has also been updated to include UEBA logs IdentityInfo and BehaviorAnalytics for contextual information around the results. \nPlease note, MFA Failed logons from known IP ranges can be benign depending on the conditional access policies. In case of noisy behavior, consider tuning the source IP ranges or location filter after careful consideration'\n",
"displayName": "MFA Rejected by User",
"enabled": true,
"entityMappings": [
{
"entityType": "Account",
"fieldMappings": [
{
"columnName": "UserPrincipalName",
"identifier": "FullName"
},
{
"columnName": "Name",
"identifier": "Name"
},
{
"columnName": "UPNSuffix",
"identifier": "UPNSuffix"
}
]
},
{
"entityType": "Account",
"fieldMappings": [
{
"columnName": "UserId",
"identifier": "AadUserId"
}
]
},
{
"entityType": "IP",
"fieldMappings": [
{
"columnName": "FailedIPAddress",
"identifier": "Address"
}
]
}
],
"OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/MFARejectedbyUser.yaml",
"query": "let riskScoreCutoff = 3; //Adjust this score threshold based on volume of results. Activities identified as the most abnormal receive the highest scores (on a scale of 0-10)\nSigninLogs\n| where ResultType == 500121\n| extend additionalDetails_ = tostring(Status.additionalDetails)\n| extend UserPrincipalName = tolower(UserPrincipalName)\n| where additionalDetails_ =~ \"MFA denied; user declined the authentication\" or additionalDetails_ has \"fraud\"\n| summarize StartTime = min(TimeGenerated), EndTIme = max(TimeGenerated) by UserPrincipalName, UserId, AADTenantId, FailedIPAddress = IPAddress\n| extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])\n| join kind=leftouter (\n IdentityInfo\n | summarize LatestReportTime = arg_max(TimeGenerated, *) by AccountUPN\n | project AccountUPN, Tags, JobTitle, GroupMembership, AssignedRoles, UserType, IsAccountEnabled\n | summarize\n Tags = make_set(Tags, 1000),\n GroupMembership = make_set(GroupMembership, 1000),\n AssignedRoles = make_set(AssignedRoles, 1000),\n UserType = make_set(UserType, 1000),\n UserAccountControl = make_set(UserType, 1000)\n by AccountUPN\n | extend UserPrincipalName=tolower(AccountUPN)\n) on UserPrincipalName\n//Below it will be joined with BehaviorAnalytics table to the Failed IP Addresses\n| join kind=leftouter (\n BehaviorAnalytics\n | where ActivityType in (\"FailedLogOn\", \"LogOn\")\n | where isnotempty(SourceIPAddress)\n | project UsersInsights, DevicesInsights, ActivityInsights, InvestigationPriority, SourceIPAddress, UserName\n | project-rename FailedIPAddress = SourceIPAddress, Name = UserName\n | summarize\n MaxInvestigationScore = max(InvestigationPriority) // Only retrieve maximum Investigation Property score for both FailedIP and User\n by FailedIPAddress, Name)\non FailedIPAddress, Name // Joining on both IP and User so as to only return context associated with same user\n| extend UEBARiskScore = MaxInvestigationScore\n| project-away *1 // removing duplicate columns post outer join from output\n| where UEBARiskScore > riskScoreCutoff\n| sort by UEBARiskScore desc \n",
"queryFrequency": "PT1H",
"queryPeriod": "PT1H",
"severity": "Medium",
"status": "Available",
"subTechniques": [
"T1078.004"
],
"suppressionDuration": "PT1H",
"suppressionEnabled": false,
"tactics": [
"InitialAccess"
],
"tags": [
"AADSecOpsGuide"
],
"techniques": [
"T1078"
],
"templateVersion": "2.0.4",
"triggerOperator": "GreaterThan",
"triggerThreshold": 0
},
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
}
]
}