Detection rules › Kusto

PRODAFT USTA - Compromised credential used in successful sign-in

Status
available
Severity
high
Time window
14d
Group by
MatchName
Source
github.com/Azure/Azure-Sentinel

Correlates corporate credentials exposed in PRODAFT USTA Account Takeover Prevention tickets with successful Microsoft Entra ID sign-ins for the same user. A successful sign-in by an account whose credentials are known to be compromised is a strong signal of account takeover. Requires the Microsoft Entra ID (SigninLogs) connector. Username matching is best-effort (case-insensitive) because USTA may record either an email or a bare username.

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: 5da571b5-54f6-42c2-8c3e-27edd987ceec
name: PRODAFT USTA - Compromised credential used in successful sign-in
description: |
  'Correlates corporate credentials exposed in PRODAFT USTA Account Takeover Prevention
  tickets with successful Microsoft Entra ID sign-ins for the same user. A successful
  sign-in by an account whose credentials are known to be compromised is a strong signal
  of account takeover. Requires the Microsoft Entra ID (SigninLogs) connector. Username
  matching is best-effort (case-insensitive) because USTA may record either an email or a
  bare username.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: PRODAFTUstaATPCCPDefinition
    dataTypes:
      - PRODAFTUstaCompromisedCredentials_CL
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - InitialAccess
relevantTechniques:
  - T1078
  - T1555
query: |
  let lookback = 14d;
  let detection = 1h;
  let compromised =
      PRODAFTUstaCompromisedCredentials
      | where Created > ago(lookback)
      | where IsCorporate == true
      | where isnotempty(Username)
      | extend MatchName = tolower(Username)
      | summarize arg_max(TimeGenerated, TicketId, CompanyName, InfectionDate, Source) by MatchName;
  SigninLogs
  | where TimeGenerated > ago(detection)
  | where ResultType == 0
  | where isnotempty(UserPrincipalName)
  | extend MatchName = tolower(UserPrincipalName)
  | join kind=inner compromised on MatchName
  | project
      TimeGenerated,
      UserPrincipalName,
      IPAddress,
      Location,
      AppDisplayName,
      ClientAppUsed,
      TicketId,
      CompanyName,
      Source,
      InfectionDate
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
customDetails:
  Ticket: TicketId
  Company: CompanyName
  Source: Source
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1d
    matchingMethod: Selected
    groupByEntities:
      - Account
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let lookback = 14d;
let detection = 1h;

Let binding: compromised used in Stage 6

let compromised = PRODAFTUstaCompromisedCredentials
    | where Created > ago(lookback)
    | where IsCorporate == true
    | where isnotempty(Username)
    | extend MatchName = tolower(Username)
    | summarize arg_max(TimeGenerated, TicketId, CompanyName, InfectionDate, Source) by MatchName;

Stage 1: source

SigninLogs

Stage 2: where

| where TimeGenerated > ago(detection)

Stage 3: where

| where ResultType == 0

Stage 4: where

| where isnotempty(UserPrincipalName)

Stage 5: extend

| extend MatchName = tolower(UserPrincipalName)

Stage 6: join

| join kind=inner compromised on MatchName

Stage 7: project

| project
    TimeGenerated,
    UserPrincipalName,
    IPAddress,
    Location,
    AppDisplayName,
    ClientAppUsed,
    TicketId,
    CompanyName,
    Source,
    InfectionDate

Stage 8: summarize aggregation inside the join branch

summarize by MatchName

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
MatchNamesummarize