Detection rules › Kusto

F&O - Unusual sign-in activity using single factor authentication

Status
available
Severity
low
Time window
14d
Group by
AppDisplayName, IPAddress, Location, UserPrincipalName
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Initial Access
Credential Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

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));

Let binding: historical_sign_in_locations used in Stage 5

let historical_sign_in_locations = historical_sign_in_activity
    | summarize by Location;

Let binding: multifactor_sign_in_count used in Stage 4

let multifactor_sign_in_count = toscalar(historical_sign_in_activity
    | where AppId in (appid_list) and ResultType == 0
    | where AuthenticationRequirement == "multiFactorAuthentication"
    | summarize count());

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

Stage 5: where

| where Location !in (historical_sign_in_locations)

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

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
Locationeqhistorical_sign_in_locationsexcludes:Location field:"Location" value:"historical_sign_in_locations"
NetworkLocationDetailsmatchtrustedNamedLocationexcludes:NetworkLocationDetails field:"NetworkLocationDetails" value:"trustedNamedLocation"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AppIdin
  • 00000015-0000-0000-c000-000000000000
  • 913c6de4-2a4a-4a61-a9ce-945d2b2ce2e0
field:"AppId" kind:in
ResultTypeeq
  • 0
field:"ResultType" kind:eq value:"0"
multifactor_sign_in_countgt
  • 0
field:"multifactor_sign_in_count" kind:gt value:"0"

Output fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
AppDisplayNameproject
CloudAppIdproject
IPAddressproject
Locationproject
UPNSuffixproject
UserPrincipalNameproject