Detection rules › Kusto
[Deprecated] Explicit MFA Deny
'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
| Tactic | Techniques |
|---|---|
| Credential Access | T1110 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.
| Field | Kind | Values |
|---|---|---|
OnboardingStatus | eq |
|
SensorHealthState | eq |
|