Detection rules › Kusto

[Deprecated] Explicit MFA Deny

Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'User explicitly denies MFA push, indicating that login was not expected and the account's password may be compromised. This rule is deprecated as of July-2024. Alternative rule with similar logic and contex from more data source is available at https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/MFARejectedbyUser.yaml'

MITRE ATT&CK coverage

TacticTechniques
Credential AccessT1110 Brute Force

Rule body kusto

id: a22740ec-fc1e-4c91-8de6-c29c6450ad00
name: '[Deprecated] Explicit MFA Deny'
description: |
  'User explicitly denies MFA push, indicating that login was not expected and the account's password may be compromised.
  This rule is deprecated as of July-2024. Alternative rule with similar logic and contex from more data source 
  is available at https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft%20Entra%20ID/Analytic%20Rules/MFARejectedbyUser.yaml'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceInfo
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let aadFunc = (tableName: string) {
      table(tableName)
      | where ResultType == 500121
      | where Status has "MFA Denied; user declined the authentication" or Status has "MFA denied; Phone App Reported Fraud"
      | extend Type = Type, PublicIP = IPAddress
      | extend
          Name = tostring(split(UserPrincipalName, '@', 0)[0]),
          UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
  };
  let aadSignin = aadFunc("SigninLogs");
  let dvcInfo = DeviceInfo
      | extend SensorHealthState = column_ifexists("SensorHealthState", "")
      | where OnboardingStatus == "Onboarded" and SensorHealthState == "Active"
      | project PublicIP, AadDeviceId;
  let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
  union isfuzzy=true aadSignin, aadNonInt
  | join kind=leftouter dvcInfo on PublicIP
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: PublicIP
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: ResourceId
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: AppDisplayName
      - identifier: AppId
        columnName: AppId
version: 1.0.7
kind: Scheduled

Stages and Predicates

Parameters

let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");

Let binding: aadFunc

let aadFunc = (tableName: string) {
    table(tableName)
    | where ResultType == 500121
    | where Status has "MFA Denied; user declined the authentication" or Status has "MFA denied; Phone App Reported Fraud"
    | extend Type = Type, PublicIP = IPAddress
    | extend
        Name = tostring(split(UserPrincipalName, '@', 0)[0]),
        UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
};

Let binding: dvcInfo

let dvcInfo = DeviceInfo
    | extend SensorHealthState = column_ifexists("SensorHealthState", "")
    | where OnboardingStatus == "Onboarded" and SensorHealthState == "Active"
    | project PublicIP, AadDeviceId;

union isfuzzy=true (2 sources)

Each leg below queries one source; the rule matches if any leg does. Sources: aadSignin, aadNonInt

Leg 1: aadSignin

Leg 2: aadNonInt

Applied to the combined result

| join kind=leftouter dvcInfo on PublicIP

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
OnboardingStatuseq
  • Onboarded transforms: cased
SensorHealthStateeq
  • Active transforms: cased