Detection rules › Kusto
F&O - Unusual sign-in activity using single factor authentication
Identifies sucessful sign-in events to Finance & Operations and Lifecycle Services using single factor/password authentication. Sign-in events from tenants not using MFA, coming from a Microsoft Entra trusted network location, or from geolocations seen previously in the last 14 days are excluded.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1078 Valid Accounts |
| Credential Access | T1552 Unsecured Credentials |
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Many Failed SignIns (Panther)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Detect non-admin requesting token for admin applications (Kusto)
Rule body kusto
id: 919e939f-95e2-4978-846e-13a721c89ea1
kind: Scheduled
name: F&O - Unusual sign-in activity using single factor authentication
description: Identifies sucessful sign-in events to Finance & Operations and Lifecycle
Services using single factor/password authentication. Sign-in events from tenants
not using MFA, coming from a Microsoft Entra trusted network location, or from geolocations
seen previously in the last 14 days are excluded.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- InitialAccess
relevantTechniques:
- T1552
- T1078
query: |
// Dynamics Lifecycle services: 913c6de4-2a4a-4a61-a9ce-945d2b2ce2e0
// Microsoft Dynamics ERP: 00000015-0000-0000-c000-000000000000
let appid_list = dynamic(["913c6de4-2a4a-4a61-a9ce-945d2b2ce2e0", "00000015-0000-0000-c000-000000000000"]);
let query_frequency = 1h;
let query_lookback = 14d;
let historical_sign_in_activity = SigninLogs
| where TimeGenerated between (ago(query_lookback) .. ago(query_frequency));
let historical_sign_in_locations = historical_sign_in_activity
| summarize by Location;
let multifactor_sign_in_count = toscalar(historical_sign_in_activity
| where AppId in (appid_list) and ResultType == 0
| where AuthenticationRequirement == "multiFactorAuthentication"
| summarize count());
SigninLogs
| where TimeGenerated >= ago(query_frequency)
| where AppId in (appid_list) and ResultType == 0
| where multifactor_sign_in_count > 0
| where Location !in (historical_sign_in_locations)
| where NetworkLocationDetails !has "trustedNamedLocation"
| summarize by UserPrincipalName, AppDisplayName, IPAddress, Location
| extend
CloudAppId = 32780,
AccountName = tostring(split(UserPrincipalName, "@")[0]),
UPNSuffix = tostring(split(UserPrincipalName, "@")[1])
| project
UserPrincipalName,
AppDisplayName,
IPAddress,
Location,
CloudAppId,
AccountName,
UPNSuffix
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: CloudAppId
alertDetailsOverride:
alertDisplayNameFormat: Dynamics 365 F&O - Unusual sign-in without multi-factor
authentication
alertDescriptionFormat: Successful sign in by {{UserPrincipalName}} to {{AppDisplayName}}
from location {{Location}} which has not been seen before in the last 14 days.
version: 3.2.0
Stages and Predicates
Parameters
let appid_list = dynamic(["913c6de4-2a4a-4a61-a9ce-945d2b2ce2e0", "00000015-0000-0000-c000-000000000000"]);
let query_frequency = 1h;
let query_lookback = 14d;
Let binding: historical_sign_in_activity
let historical_sign_in_activity = SigninLogs
| where TimeGenerated between (ago(query_lookback) .. ago(query_frequency));
Derived from query_frequency, query_lookback.
Let binding: historical_sign_in_locations
let historical_sign_in_locations = historical_sign_in_activity
| summarize by Location;
Derived from historical_sign_in_activity.
Let binding: multifactor_sign_in_count
let multifactor_sign_in_count = toscalar(historical_sign_in_activity
| where AppId in (appid_list) and ResultType == 0
| where AuthenticationRequirement == "multiFactorAuthentication"
| summarize count());
Derived from appid_list, historical_sign_in_activity.
Stage 1: source
SigninLogs
Stage 2: where
| where TimeGenerated >= ago(query_frequency)
Stage 3: where
| where AppId in (appid_list) and ResultType == 0
Stage 4: where
| where multifactor_sign_in_count > 0
References multifactor_sign_in_count (defined above).
Stage 5: where
| where Location !in (historical_sign_in_locations)
References historical_sign_in_locations (defined above).
Stage 6: where
| where NetworkLocationDetails !has "trustedNamedLocation"
Stage 7: summarize
| summarize by UserPrincipalName, AppDisplayName, IPAddress, Location
Stage 8: extend
| extend
CloudAppId = 32780,
AccountName = tostring(split(UserPrincipalName, "@")[0]),
UPNSuffix = tostring(split(UserPrincipalName, "@")[1])
Stage 9: project
| project
UserPrincipalName,
AppDisplayName,
IPAddress,
Location,
CloudAppId,
AccountName,
UPNSuffix
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
Location | eq | historical_sign_in_locations |
NetworkLocationDetails | match | trustedNamedLocation |
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 |
|---|---|---|
AppId | in |
|
ResultType | eq |
|
multifactor_sign_in_count | gt |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
AccountName | project |
AppDisplayName | project |
CloudAppId | project |
IPAddress | project |
Location | project |
UPNSuffix | project |
UserPrincipalName | project |