Microsoft Sentinel Analytic Rules
cloudbrothers.infoAzure Sentinel RepoToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

MFA Rejected by User

Back
Idd99cf5c3-d660-436c-895b-8a8f8448da23
RulenameMFA Rejected by User
DescriptionIdentifies 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
SeverityMedium
TacticsInitialAccess
TechniquesT1078.004
Required data connectorsAzureActiveDirectory
BehaviorAnalytics
KindScheduled
Query frequency1h
Query period1h
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/MFARejectedbyUser.yaml
Version2.0.4
Arm templated99cf5c3-d660-436c-895b-8a8f8448da23.json
Deploy To Azure
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 
id: d99cf5c3-d660-436c-895b-8a8f8448da23
tactics:
- InitialAccess
queryPeriod: 1h
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/MFARejectedbyUser.yaml
triggerThreshold: 0
name: MFA Rejected by User
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   
severity: Medium
triggerOperator: gt
kind: Scheduled
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
queryFrequency: 1h
requiredDataConnectors:
- connectorId: AzureActiveDirectory
  dataTypes:
  - SigninLogs
- connectorId: BehaviorAnalytics
  dataTypes:
  - BehaviorAnalytics
- connectorId: BehaviorAnalytics
  dataTypes:
  - IdentityInfo
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'  
status: Available
version: 2.0.4
entityMappings:
- fieldMappings:
  - columnName: UserPrincipalName
    identifier: FullName
  - columnName: Name
    identifier: Name
  - columnName: UPNSuffix
    identifier: UPNSuffix
  entityType: Account
- fieldMappings:
  - columnName: UserId
    identifier: AadUserId
  entityType: Account
- fieldMappings:
  - columnName: FailedIPAddress
    identifier: Address
  entityType: IP
{
  "$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"
    }
  ]
}