← Back to SOC feed Coverage →

Account created from non-approved sources

kql MEDIUM Azure-Sentinel
T1136.003
AuditLogsSigninLogs
backdoormicrosoftofficial
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-03-25T03:06:09Z · Confidence: medium

Hunt Hypothesis

Accounts created from non-approved domains may indicate adversary attempts to establish persistent access by creating compromised or malicious accounts. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential compromise and mitigate lateral movement risks.

KQL Query

let core_domains = (SigninLogs
  | where TimeGenerated > ago(7d)
  | where ResultType == 0
  | extend domain = tolower(split(UserPrincipalName, "@")[1])
  | summarize by tostring(domain));
  let alternative_domains = (SigninLogs
  | where TimeGenerated > ago(7d)
  | where isnotempty(AlternateSignInName)
  | where ResultType == 0
  | extend domain = tolower(split(AlternateSignInName, "@")[1])
  | summarize by tostring(domain));
  AuditLogs
  | where TimeGenerated > ago(1d)
  | where OperationName =~ "Add User"
  | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
  | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
  | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
  | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
  | extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
  | extend UserAdded = tostring(TargetResources[0].userPrincipalName)
  | extend UserAddedDomain = case(
  UserAdded has "#EXT#", tostring(split(tostring(split(UserAdded, "#EXT#")[0]), "_")[1]),
  UserAdded !has "#EXT#", tostring(split(UserAdded, "@")[1]),
  UserAdded)
  | where UserAddedDomain !in (core_domains) and UserAddedDomain !in (alternative_domains)
  | extend AddedByName = case(
  InitiatingUserPrincipalName has "#EXT#", tostring(split(tostring(split(InitiatingUserPrincipalName, "#EXT#")[0]), "_")[0]),
  InitiatingUserPrincipalName !has "#EXT#", tostring(split(InitiatingUserPrincipalName, "@")[0]),
  InitiatingUserPrincipalName)
  | extend AddedByUPNSuffix = case(
  InitiatingUserPrincipalName has "#EXT#", tostring(split(tostring(split(InitiatingUserPrincipalName, "#EXT#")[0]), "_")[1]),
  InitiatingUserPrincipalName !has "#EXT#", tostring(split(InitiatingUserPrincipalName, "@")[1]),
  InitiatingUserPrincipalName)
  | extend UserAddedName = case(
  UserAdded has "#EXT#", tostring(split(tostring(split(UserAdded, "#EXT#")[0]), "_")[0]),
  UserAdded !has "#EXT#", tostring(split(UserAdded, "@")[0]),
  UserAdded)

Analytic Rule Definition

id: 99d589fa-7337-40d7-91a0-c96d0c4fa437
name: Account created from non-approved sources
description: |
  'This query looks for an account being created from a domain that is not regularly seen in a tenant.
    Attackers may attempt to add accounts from these sources as a means of establishing persistant access to an environment.
    Created accounts should be investigated to confirm expected creation.
    Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#short-lived-accounts'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
      - AuditLogs
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1136.003
tags:
  - AADSecOpsGuide
query: |
  let core_domains = (SigninLogs
    | where TimeGenerated > ago(7d)
    | where ResultType == 0
    | extend domain = tolower(split(UserPrincipalName, "@")[1])
    | summarize by tostring(domain));
    let alternative_domains = (SigninLogs
    | where TimeGenerated > ago(7d)
    | where isnotempty(AlternateSignInName)
    | where ResultType == 0
    | extend domain = tolower(split(AlternateSignInName, "@")[1])
    | summarize by tostring(domain));
    AuditLogs
    | where TimeGenerated > ago(1d)
    | where OperationName =~ "Add User"
    | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
    | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
    | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
    | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
    | extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
    | extend UserAdded = tostring(TargetResources[0].userPrincipalName)
    | extend UserAddedDomain = case(
    UserAdded has "#EXT#", tostring(split(tostring(split(UserAdded, "#EXT#")[0]), "_")[1]),
    UserAdded !has "#EXT#", tostring(split(UserAdded, "@")[1]),
    UserAdded)
    | where UserAddedDomain !in (core_domains) and UserAddedDomain !in (alternative_domains)
    | extend AddedByName = case(
    InitiatingUserPrincipalName has "#EXT#", tostring(split(tostring(split(InitiatingUserPrincipalName, "#EXT#")[0]), "_")[0]),
    InitiatingUserPrincipalName !has "#EXT#", tostring(split(InitiatingUserPrincipalName, "@")[0]),
    InitiatingUserPrincipalName)
    | extend AddedByUPNSuffix = case(
    InitiatingUserPrincipalName has "#EXT#", tostring(split(tostring(split(InitiatingUserPrincipalName, "#EXT#")[0]), "_")[1]),
    InitiatingUserPrincipalName !has "#EXT#", tostring(split(InitiatingUserPrincipalName, "@")[1]),
    InitiatingUserPrincipalName)
    | extend UserAddedName = case(
    UserAdded has "#EXT#", tostring(split(tostring(split(UserAdded, "#EXT#")[0]), "_")[0]),
    UserAdded !has "#EXT#", tostring(split(UserAdded, "@")[0]),
    Use

Required Data Sources

Sentinel TableNotes
AuditLogsEnsure 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/Detections/MultipleDataSources/Accountcreatedfromnon-approvedsources.yaml