Detection rules › Kusto
Multi-Factor Authentication Disabled for a User
Multi-Factor Authentication (MFA) helps prevent credential compromise. This alert identifies when an attempt has been made to deactivate MFA for a user.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Credential Access |
Telemetry coverage
Rules detecting the same action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created and Deleted in Short Timeframe (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Account created or deleted by non-approved user (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- ASL AWS Multi-Factor Authentication Disabled (Splunk)
- Authentication Method Changed for Privileged Account (Kusto)
Rule body
id: 65c78944-930b-4cae-bd79-c3664ae30ba7
name: Multi-Factor Authentication Disabled for a User
description: |
'Multi-Factor Authentication (MFA) helps prevent credential compromise. This alert identifies when an attempt has been made to deactivate MFA for a user.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- Persistence
relevantTechniques:
- T1098
- T1556
query: |
(union isfuzzy=true
(AuditLogs
| where OperationName =~ "Disable Strong Authentication"
| extend _parsedIntiatedByUser = parse_json(tostring(InitiatedBy.user))
| extend _parsedIntiatedByApp = parse_json(tostring(InitiatedBy.app))
| extend IPAddress = tostring(_parsedIntiatedByUser.ipAddress)
| extend InitiatedByUser = iff(isnotempty(tostring(_parsedIntiatedByUser.userPrincipalName)),
tostring(_parsedIntiatedByUser.userPrincipalName), tostring(_parsedIntiatedByApp.displayName))
| extend Targetprop = todynamic(TargetResources)
| extend TargetUser = tostring(Targetprop[0].userPrincipalName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, InitiatedByUser , Operation = OperationName , CorrelationId, IPAddress, Category, Source = SourceSystem , AADTenantId, Type
),
(AWSCloudTrail
| where EventName in~ ("DeactivateMFADevice", "DeleteVirtualMFADevice")
| extend _parsedRequestParameters = parse_json(RequestParameters)
| extend InstanceProfileName = tostring(_parsedRequestParameters.InstanceProfileName)
| extend TargetUser = tostring(_parsedRequestParameters.userName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, Source = EventSource , Operation = EventName , TenantorInstance_Detail = InstanceProfileName, IPAddress = SourceIpAddress
)
)
| extend timestamp = StartTimeUtc, UserName = tostring(split(User, '@', 0)[0]), UPNSuffix = tostring(split(User, '@', 1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: UserName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 1.0.5
kind: Scheduled
Stages and Predicates
union isfuzzy=true (2 sources)
Each leg below queries one source; the rule matches if any leg does. Sources: AuditLogs, AWSCloudTrail
Leg 1: AuditLogs
AuditLogs
| where OperationName =~ "Disable Strong Authentication"
| extend _parsedIntiatedByUser = parse_json(tostring(InitiatedBy.user))
| extend _parsedIntiatedByApp = parse_json(tostring(InitiatedBy.app))
| extend IPAddress = tostring(_parsedIntiatedByUser.ipAddress)
| extend InitiatedByUser = iff(isnotempty(tostring(_parsedIntiatedByUser.userPrincipalName)),
tostring(_parsedIntiatedByUser.userPrincipalName), tostring(_parsedIntiatedByApp.displayName))
| extend Targetprop = todynamic(TargetResources)
| extend TargetUser = tostring(Targetprop[0].userPrincipalName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, InitiatedByUser , Operation = OperationName , CorrelationId, IPAddress, Category, Source = SourceSystem , AADTenantId, Type
Leg 2: AWSCloudTrail
AWSCloudTrail
| where EventName in~ ("DeactivateMFADevice", "DeleteVirtualMFADevice")
| extend _parsedRequestParameters = parse_json(RequestParameters)
| extend InstanceProfileName = tostring(_parsedRequestParameters.InstanceProfileName)
| extend TargetUser = tostring(_parsedRequestParameters.userName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, Source = EventSource , Operation = EventName , TenantorInstance_Detail = InstanceProfileName, IPAddress = SourceIpAddress
Applied to the combined result
| extend timestamp = StartTimeUtc, UserName = tostring(split(User, '@', 0)[0]), UPNSuffix = tostring(split(User, '@', 1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventName | in |
| field:"aws::eventName" kind:in |
OperationName | eq |
| field:"OperationName" kind:eq value:"Disable Strong Authentication" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTimeUtc | summarize |
IPAddress | summarize |
Operation | summarize |
Source | summarize |
StartTimeUtc | summarize |
TenantorInstance_Detail | summarize |
User | summarize |
UPNSuffix | extend |
UserName | extend |
timestamp | extend |