Query Details

27 ROPC Dormant Account

Query

id: d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a
name: ROPC Authentication Against Dormant Account
version: 1.0.0
kind: Scheduled
description: |
  Detects a successful ROPC sign-in for an account that has had no interactive or
  non-interactive successful sign-in in the prior 30 days. Dormant accounts are a
  preferred target for credential-stuffing and breach-replay attacks: leaked passwords
  for inactive employees / contractors often still work because no one notices the
  sign-ins.

  Trigger: successful ROPC sign-in (ResultType == 0) where UPN does not appear in
  SigninLogs OR AADNonInteractiveUserSignInLogs successes in the prior 30 days.

  Tuning:
    - LegitRopcApps suppresses known-good ROPC clients.
    - Service / sync accounts that intentionally sit dormant should be allowlisted via
      LegitRopcUsers.
  MITRE ATT&CK: T1078 (Valid Accounts), T1110 (Brute Force)
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
      - SigninLogs
queryFrequency: 1h
queryPeriod: 30d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - CredentialAccess
relevantTechniques:
  - T1078
  - T1110
query: |
  let LegitRopcApps = dynamic([
      "Microsoft Authentication Broker",
      "Microsoft Intune Company Portal",
      "Azure AD Connect",
      "Microsoft Office",
      "Microsoft Office Authentication Broker"
  ]);
  let LegitRopcUsers = dynamic([]);
  let Active =
      union isfuzzy=true
          (SigninLogs
              | where TimeGenerated between (ago(30d) .. ago(1h))),
          (AADNonInteractiveUserSignInLogs
              | where TimeGenerated between (ago(30d) .. ago(1h)))
      | where ResultType == 0
      | distinct Upn = tolower(UserPrincipalName);
  AADNonInteractiveUserSignInLogs
  | invoke ExcludeAllowlistedIPs_AADNI()
  | where TimeGenerated > ago(1h)
  | where AuthenticationProtocol =~ "ropc"
  | where ResultType == 0
  | where AppDisplayName !in~ (LegitRopcApps)
  | extend Upn = tolower(UserPrincipalName)
  | where Upn !in (LegitRopcUsers)
  | where Upn !in (Active)
  | summarize
      Count     = count(),
      IPs       = make_set(IPAddress),
      Countries = make_set(Location),
      Apps      = make_set(AppDisplayName),
      FirstSeen = min(TimeGenerated),
      LastSeen  = max(TimeGenerated)
    by UserPrincipalName
  | extend IPAddress = tostring(IPs[0])
  | order by Count desc
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
customDetails:
  SignInCount: Count
  Countries: Countries
  Apps: Apps
alertDetailsOverride:
  alertDisplayNameFormat: "ROPC sign-in on dormant account {{UserPrincipalName}}"
  alertDescriptionFormat: "Dormant account {{UserPrincipalName}} (no successful sign-in in 30 days) suddenly authenticated via ROPC from {{Countries}}. Strong indicator of breach-replay or credential-stuffing success."
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AnyAlert
    groupByEntities:
      - Account
    groupByAlertDetails: []
    groupByCustomDetails: []

Explanation

This query is designed to detect suspicious activity involving dormant accounts in an Azure Active Directory environment. Here's a simplified explanation:

  1. Purpose: The query identifies successful Resource Owner Password Credential (ROPC) sign-ins for accounts that haven't had any successful interactive or non-interactive sign-ins in the past 30 days. Dormant accounts are often targeted in credential-stuffing or breach-replay attacks because their inactivity can go unnoticed.

  2. Trigger: The detection is triggered by a successful ROPC sign-in (where ResultType is 0) for a user principal name (UPN) that hasn't appeared in the sign-in logs or non-interactive sign-in logs in the last 30 days.

  3. Tuning:

    • Known legitimate ROPC applications are excluded from triggering alerts.
    • Service or sync accounts that are intentionally dormant can be allowlisted to prevent false positives.
  4. Severity and Techniques: The alert is marked as high severity and is associated with MITRE ATT&CK techniques T1078 (Valid Accounts) and T1110 (Brute Force).

  5. Data Sources: The query uses data from Azure Active Directory, specifically the AADNonInteractiveUserSignInLogs and SigninLogs.

  6. Query Logic:

    • It first defines lists of legitimate ROPC applications and users.
    • It checks for any sign-ins in the past 30 days to determine active accounts.
    • It filters out sign-ins from known legitimate apps and allowlisted users.
    • It identifies ROPC sign-ins from accounts not active in the last 30 days and summarizes the findings, including the number of sign-ins, IP addresses, countries, and applications involved.
  7. Alert and Incident Configuration:

    • Alerts are generated with a specific format indicating the dormant account and the nature of the suspicious activity.
    • Incidents are created for these alerts, with configuration options for grouping and managing incidents.

Overall, this query helps security teams identify potentially compromised accounts that have been inactive, allowing them to take appropriate action to secure the environment.

Details

David Alonso profile picture

David Alonso

Released: May 29, 2026

Tables

SigninLogsAADNonInteractiveUserSignInLogs

Keywords

AzureActiveDirectoryAADNonInteractiveUserSignInLogsSigninLogsUserPrincipalNameIPAddressLocationAppDisplayNameMicrosoftAuthenticationBrokerMicrosoftIntuneCompanyPortalAzureADConnectMicrosoftOfficeMicrosoftOfficeAuthenticationBrokerAccountIP

Operators

letdynamicunionisfuzzybetweenagodistincttolowerinvokewhere=~!in~extend!insummarizecountmake_setminmaxbytostringorder bydesc

Actions