← Back to SOC feed Coverage →

User Accounts - Blocked Accounts

kql MEDIUM Azure-Sentinel
T1078
AADNonInteractiveUserSignInLogsSigninLogs
backdoorhuntingmicrosoftofficial
This rule was pulled from an open-source repository and enriched with AI. Validate in a test environment before deploying to production.
View original rule at Azure-Sentinel →
Retrieved: 2026-06-04T23:00:00Z · Confidence: medium

Hunt Hypothesis

Adversaries may block legitimate user accounts to mask their own persistent access or to evade detection by disrupting normal account activity. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential account compromise or adversarial manipulation of account states.

KQL Query

let starttime = totimespan('{{StartTimeISO}}');
let endtime = totimespan('{{EndTimeISO}}');
let lookback = starttime - 7d;
let isGUID = "[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}";
let aadFunc = (tableName:string){
  table(tableName)
  | where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
  | where not(Identity matches regex isGUID)
};
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
let blocked_users = 
union isfuzzy=true aadSignin, aadNonInt 
// Blocked or locked account due to failed attempts for various reasons.
| where ResultType != "0"
| where ResultDescription has_any ("blocked", "locked")  or ResultType in (50053, 50131, 53003, 500121)
| summarize FirstBlockedAttempt = min(TimeGenerated), LastBlockedAttempt = max(TimeGenerated) by UserPrincipalName, ResultDescription, ResultType;
blocked_users
| join kind= inner (
union isfuzzy=true aadSignin, aadNonInt
| where ResultType == 0
| summarize FirstSuccessfulSignin = min(TimeGenerated), LastSuccessfulSignin = max(TimeGenerated), make_set(IPAddress), make_set(ClientAppUsed), make_set(UserAgent), make_set(AppDisplayName) by UserPrincipalName, UserDisplayName
) on UserPrincipalName
| where LastSuccessfulSignin > LastBlockedAttempt //Checking if successul login is after lastblockedattempts
| extend timestamp = LastSuccessfulSignin, AccountCustomEntity = UserPrincipalName

Analytic Rule Definition

id: dbc82bc1-c7df-44e3-838a-5846a313cf35
name: User Accounts - Blocked Accounts
description: |
  'An account could be blocked/locked out due to multiple reasons. This hunting query summarize blocked/lockout accounts and checks if most recent signin events for them is after last blocked accounts
  Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-successful-unusual-sign-ins'
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
tags:
  - AADSecOpsGuide
query: |
  let starttime = totimespan('{{StartTimeISO}}');
  let endtime = totimespan('{{EndTimeISO}}');
  let lookback = starttime - 7d;
  let isGUID = "[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}";
  let aadFunc = (tableName:string){
    table(tableName)
    | where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
    | where not(Identity matches regex isGUID)
  };
  let aadSignin = aadFunc("SigninLogs");
  let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
  let blocked_users = 
  union isfuzzy=true aadSignin, aadNonInt 
  // Blocked or locked account due to failed attempts for various reasons.
  | where ResultType != "0"
  | where ResultDescription has_any ("blocked", "locked")  or ResultType in (50053, 50131, 53003, 500121)
  | summarize FirstBlockedAttempt = min(TimeGenerated), LastBlockedAttempt = max(TimeGenerated) by UserPrincipalName, ResultDescription, ResultType;
  blocked_users
  | join kind= inner (
  union isfuzzy=true aadSignin, aadNonInt
  | where ResultType == 0
  | summarize FirstSuccessfulSignin = min(TimeGenerated), LastSuccessfulSignin = max(TimeGenerated), make_set(IPAddress), make_set(ClientAppUsed), make_set(UserAgent), make_set(AppDisplayName) by UserPrincipalName, UserDisplayName
  ) on UserPrincipalName
  | where LastSuccessfulSignin > LastBlockedAttempt //Checking if successul login is after lastblockedattempts
  | extend timestamp = LastSuccessfulSignin, AccountCustomEntity = UserPrincipalName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
version: 1.0.0

Required Data Sources

Sentinel TableNotes
AADNonInteractiveUserSignInLogsEnsure this data connector is enabled
SigninLogsEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

Original source: https://github.com/Azure/Azure-Sentinel/blob/main/Hunting Queries/SigninLogs/UserAccounts-BlockedAccounts.yaml