Detection rules › Kusto

Failed Logins from Unknown or Invalid User

Status
available
Severity
medium
Time window
1h
Group by
City, ClientIP, Country, actor_alternateId_s
Source
github.com/Azure/Azure-Sentinel

This query searches for numerous login attempts to the management console with an unknown or invalid user name.

MITRE ATT&CK coverage

TacticTechniques
Credential AccessT1110 Brute Force

Event coverage

Rules detecting the same action

Other rules on this platform that filter on the same API call or operation.

Rule body kusto

id: 884be6e7-e568-418e-9c12-89229865ffde
name: Failed Logins from Unknown or Invalid User
description: |
  'This query searches for numerous login attempts to the management console with an unknown or invalid user name.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: OktaSSO
    dataTypes:
      - Okta_CL
  - connectorId: OktaSSOv2
    dataTypes:
      - OktaSSO
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let FailureThreshold = 15;
  let FailedLogins = OktaSSO
  | where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"
  | summarize count() by actor_alternateId_s, client_ipAddress_s, bin(TimeGenerated, 5m)
  | where count_ > FailureThreshold
  | project client_ipAddress_s, actor_alternateId_s;
  OktaSSO
  | join kind=inner (FailedLogins) on client_ipAddress_s, actor_alternateId_s
  | where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"
  | summarize count() by actor_alternateId_s, ClientIP = client_ipAddress_s, City = column_ifexists('client_geographicalContext_city_s', ""), Country = column_ifexists('client_geographicalContext_country_s', ""), column_ifexists('published_t', now())
  | sort by column_ifexists('published_t', now()) desc
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: actor_alternateId_s
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
version: 1.1.1
kind: Scheduled

Stages and Predicates

Parameters

let FailureThreshold = 15;

Let binding: FailedLogins

let FailedLogins = OktaSSO
| where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"
| summarize count() by actor_alternateId_s, client_ipAddress_s, bin(TimeGenerated, 5m)
| where count_ > FailureThreshold
| project client_ipAddress_s, actor_alternateId_s;

Derived from FailureThreshold.

Stage 1: source

OktaSSO

Stage 2: join

| join kind=inner (FailedLogins) on client_ipAddress_s, actor_alternateId_s

Stage 3: where

| where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"

Stage 4: summarize

| summarize count() by actor_alternateId_s, ClientIP = client_ipAddress_s, City = column_ifexists('client_geographicalContext_city_s', ""), Country = column_ifexists('client_geographicalContext_country_s', ""), column_ifexists('published_t', now())

Stage 5: sort

| sort by column_ifexists('published_t', now()) desc

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
count_gt
  • 15 transforms: cased
eventType_seq
  • user.session.start
outcome_reason_seq
  • VERIFICATION_ERROR

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.

FieldSource
Citysummarize
ClientIPsummarize
Countrysummarize
actor_alternateId_ssummarize