Rare domain accounts accessing cloud resources may indicate adversarial activity leveraging compromised or stealthy credentials, as these domains are unlikely to be associated with legitimate user behavior. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential credential compromise or lateral movement attempts.
KQL Query
// Provide customLimit value with default above domainLimit value so it will not block unless changed
let customLimit = 11;
let domainLimit = 10;
let domainLookback = union isfuzzy=true
(AuditLogs
| extend UserPrincipalName = tolower(tostring(TargetResources.[0].userPrincipalName))
// parse out AuditLog values for various locations where UPN could be
| extend UserPrincipalName = iff(isnotempty(UserPrincipalName),
UserPrincipalName,
iif((tostring(InitiatedBy.user.userPrincipalName)=='unknown'),
extract("Email: ([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+)", 1, tostring(parse_json(TargetResources)[0].displayName)),
InitiatedBy.user.userPrincipalName))
| where UserPrincipalName has "@" or UserPrincipalName startswith "NT AUTHORITY"
| extend RareDomain = toupper(tostring(split(UserPrincipalName, "@")[-1]))
| where isnotempty(RareDomain)
| summarize RareDomainCount = count() by Type, RareDomain,UserPrincipalName
| where RareDomainCount <= domainLimit
| extend AccountCustomEntity = UserPrincipalName
// remove comment from below if you would like to have a lower limit for RareDomainCount specific to AuditLog
//| where RareDomainCount <= customLimit
),
(OfficeActivity
| extend UserPrincipalName = tolower(UserId)
| where UserPrincipalName has "@" or UserPrincipalName startswith "NT AUTHORITY"
| extend RareDomain = toupper(tostring(split(UserPrincipalName, "@")[-1]))
| summarize RareDomainCount = count() by Type, RareDomain, UserPrincipalName
| where RareDomainCount <= domainLimit
| extend AccountCustomEntity = UserPrincipalName
// remove comment from below if you would like to have a lower limit for RareDomainCount specific to OfficeActivity
//| where RareDomainCount <= customLimit
),
(SigninLogs
| where UserPrincipalName has "@" or UserPrincipalName startswith "NT AUTHORITY"
| extend RareDomain = toupper(tostring(split(UserPrincipalName, "@")[-1]))
| summarize RareDomainCount = count() by Type, RareDomain
| where RareDomainCount <= domainLimit
// remove comment from below if you would like to have a lower limit for RareDomainCount specific to SigninLogs
//| where RareDomainCount <= customLimit
);
let AuditLogsRef = domainLookback | join (
AuditLogs
| extend UserPrincipalName = tolower(tostring(TargetResources.[0].userPrincipalName))
| extend UserPrincipalName = iff(isempty(UserPrincipalName), tostring(InitiatedBy.user.userPrincipalName), UserPrincipalName)
| extend RareDomain = toupper(tostring(split(UserPrincipalName, "@")[-1]))
| where isnotempty(RareDomain)
| summarize UPNRefCount = count() by TimeGenerated, Type, RareDomain, UserPrincipalName, OperationName, Category, Result
| extend AccountCustomEntity = UserPrincipalName
) on Type, RareDomain;
let OfficeActivityRef = domainLookback | join (
OfficeActivity
| extend UserPrincipalName = tolower(UserId)
| where UserPrincipalName has "@" or UserPrincipalName startswith "NT AUTHORITY"
| extend RareDomain = toupper(tostring(split(UserPrincipalName, "@")[-1]))
| summarize UPNRefCount = count() by TimeGenerated, Type, RareDomain, UserPrincipalName, OperationName = Operation, Category = OfficeWorkload, Result = ResultStatus
| extend AccountCustomEntity = UserPrincipalName
) on Type, RareDomain;
let SigninLogsRef = domainLookback | join (
SigninLogs
| extend UserPrincipalName = tolower(UserId)
| where UserPrincipalName has "@" or UserPrincipalName startswith "NT AUTHORITY"
| extend RareDomain = toupper(tostring(split(UserPrincipalName, "@")[-1]))
| summarize UPNRefCount = count() by TimeGenerated, Type, RareDomain, UserPrincipalName, OperationName, Category = AppDisplayName, Result = ResultType
| extend AccountCustomEntity = UserPrincipalName
) on Type, RareDomain;
let Results = union isfuzzy=true
AuditLogsRef,OfficeActivityRef,SigninLogsRef;
Results | project TimeGenerated, Type, RareDomain, UserPrincipalName, OperationName, Category, Result, UPNRefCount
| order by TimeGenerated asc
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName
id: 66fb97d1-55c3-4268-ac22-b9742d0fdccc
name: Rare domains seen in Cloud Logs
description: |
'This script identifies rare domain accounts accessing cloud resources by examining logs. You can lower the domainLimit value to see domains with fewer access attempts. For example, set domainLimit = 2 to see domains with 2 or fewer access attempts.'
description_detailed: |
'This will identify rare domain accounts accessing or attempting to access cloud resources by examining the AuditLogs, OfficeActivity and SigninLogs
Rare does not mean malicious, but it may be something you would be interested in investigating further
Additionally, it is possible that there may be many domains if you have allowed access by 3rd party domain accounts.
Lower the domainLimit value as needed. For example, if you only want to see domains that have an access attempt count of 2 or less,
then set domainLimit = 2 below. If you need to set it lower only for a given log, then use customLimit in the same way and uncomment
that line in the script.'
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity
tactics:
- InitialAccess
- Discovery
- Collection
relevantTechniques:
- T1190
- T1087
- T1114
query: |
// Provide customLimit value with default above domainLimit value so it will not block unless changed
let customLimit = 11;
let domainLimit = 10;
let domainLookback = union isfuzzy=true
(AuditLogs
| extend UserPrincipalName = tolower(tostring(TargetResources.[0].userPrincipalName))
// parse out AuditLog values for various locations where UPN could be
| extend UserPrincipalName = iff(isnotempty(UserPrincipalName),
UserPrincipalName,
iif((tostring(InitiatedBy.user.userPrincipalName)=='unknown'),
extract("Email: ([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+)", 1, tostring(parse_json(TargetResources)[0].displayName)),
InitiatedBy.user.userPrincipalName))
| where UserPrincipalName has "@" or UserPrincipalName startswith "NT AUTHORITY"
| extend RareDomain = toupper(tostring(split(UserPrincipalName, "@")[-1]))
| where isnotempty(RareDomain)
| summarize RareDomainCount = count() by Type, RareDomain,UserPrincipalName
| where RareDomainCount <= domainLimit
| extend AccountCustomEntity = UserPrincipalName
// remove comment from below if you would like to have a lower limit for RareDomainCount specific to AuditLog
//| where RareDomainCount <= customLimit
),
(OfficeActivity
| extend UserPrincipalName = tolower(UserId)
| where UserPrincipalName has "@" or UserPrincipalName startswith "NT AUTHORITY"
| extend RareDomain = toupper(tostring(split(UserPrincipalName, "@")[-1]))
| summarize RareDomainCount = count() by Type, RareDomain, UserPrincipalName
| where RareDomainCount <= domainLimit
| extend AccountCustomEntity = UserPrincipalName
// remove comment from below if you would like t
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
OfficeActivity | Ensure this data connector is enabled |
SigninLogs | Ensure this data connector is enabled |
Scenario: Scheduled Job for System Maintenance
Description: A legitimate scheduled job (e.g., Task Scheduler or cron job) runs a maintenance script that accesses cloud resources using a domain account with minimal activity.
Filter/Exclusion: Exclude domains associated with scheduled jobs or system maintenance tasks (e.g., DOMAIN\maintenance, DOMAIN\sysadmin).
Scenario: Log Collection Agent Communication
Description: A log collection agent (e.g., Splunk Universal Forwarder, ELK Stack, or Datadog Agent) authenticates to a cloud service (e.g., AWS CloudWatch, Azure Log Analytics) using a domain account.
Filter/Exclusion: Exclude domains used by log collection tools (e.g., DOMAIN\logagent, DOMAIN\monitoring).
Scenario: Admin Task for User Provisioning
Description: An admin account (e.g., DOMAIN\Administrator, DOMAIN\ITAdmin) accesses cloud resources to provision or deprovision users, which may result in rare domain access.
Filter/Exclusion: Exclude domains associated with admin accounts or user provisioning tasks (e.g., DOMAIN\Administrator, DOMAIN\IT).
Scenario: Backup Job Accessing Cloud Storage
Description: A backup job (e.g., Veeam, Commvault, or AWS Backup) uses a domain account to access cloud storage for backups, resulting in infrequent access.
Filter/Exclusion: Exclude domains used by backup services (e.g., DOMAIN\backup, DOMAIN\backupsvc).
Scenario: Temporary Access for Third-Party Support
Description: A third-party support account (e.g., DOMAIN\Support, DOMAIN\ThirdParty) accesses cloud resources temporarily for troubleshooting or support.
*Filter/Ex