Detection rules › Kusto

New onmicrosoft domain added to tenant

Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

This detection looks for new onmicrosoft domains being added to a tenant. An attacker who compromises a tenant may register a new onmicrosoft domain in order to masquerade as a service provider for launching phishing campaigns. Domain additions are not a common occurrence and users should validate that the domain was added by a legitimate user, with a legitimate purpose.

MITRE ATT&CK coverage

TacticTechniques
Resource DevelopmentT1585.003 Establish Accounts: Cloud Accounts

Event coverage

Rule body kusto

id: 4f42b94f-b210-42d1-a023-7fa1c51d969f
name: New onmicrosoft domain added to tenant
description: |
  'This detection looks for new onmicrosoft domains being added to a tenant. 
  An attacker who compromises a tenant may register a new onmicrosoft domain in order to masquerade as a service provider for launching phishing campaigns.
  Domain additions are not a common occurrence and users should validate that the domain was added by a legitimate user, with a legitimate purpose.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - ResourceDevelopment
relevantTechniques:
  - T1585.003
query: |
  AuditLogs
  | where AADOperationType == "Add"
  | where Result == "success"
  | where OperationName in ("Add verified domain", "Add unverified domain")
  | extend InitiatedBy = parse_json(InitiatedBy)
  | extend InitiatingUser = tostring(InitiatedBy.user.userPrincipalName)
  | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
  | extend InitiatingIp = tostring(InitiatedBy.user.ipAddress)
  | extend InitiatingApp = tostring(InitiatedBy.app.displayName)
  | extend InitiatingSPID = tostring(InitiatedBy.app.servicePrincipalId)
  | extend DomainAdded = tostring(TargetResources[0].displayName)
  | where DomainAdded has "onmicrosoft"
  | extend ActionInitiatedBy = case(isnotempty(InitiatingUser), InitiatingUser, strcat(InitiatingApp, " - ", InitiatingSPID))
  | extend UserName = split(InitiatingUser, "@")[0]
  | extend UPNSuffix = split(InitiatingUser, "@")[1]
  | project-reorder TimeGenerated, OperationName, DomainAdded, ActionInitiatedBy, InitiatingIp
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUser
      - identifier: Name
        columnName: UserName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingSPID
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIp
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: DomainAdded
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: '{{DomainAdded}} added to tenant by {{ActionInitiatedBy}}'
  alertDescriptionFormat: 'This detection looks for new onmicrosoft domains being added to a tenant. An attacker who compromises a tenant may register a new onmicrosoft domain in order to masquerade as a service provider for launching phishing accounts. Domain additions are not a common occurrence and users should validate that {{ActionInitiatedBy}} added {{DomainAdded}} with a legitimate purpose.'
kind: Scheduled
version: 1.0.1

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where AADOperationType == "Add"

Stage 3: where

| where Result == "success"

Stage 4: where

| where OperationName in ("Add verified domain", "Add unverified domain")

Stage 5: extend (7 consecutive steps)

| extend InitiatedBy = parse_json(InitiatedBy)
| extend InitiatingUser = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIp = tostring(InitiatedBy.user.ipAddress)
| extend InitiatingApp = tostring(InitiatedBy.app.displayName)
| extend InitiatingSPID = tostring(InitiatedBy.app.servicePrincipalId)
| extend DomainAdded = tostring(TargetResources[0].displayName)

Stage 6: where

| where DomainAdded has "onmicrosoft"

Stage 7: extend (3 consecutive steps)

| extend ActionInitiatedBy = case(isnotempty(InitiatingUser), InitiatingUser, strcat(InitiatingApp, " - ", InitiatingSPID))
| extend UserName = split(InitiatingUser, "@")[0]
| extend UPNSuffix = split(InitiatingUser, "@")[1]
ActionInitiatedBy =
ifisnotempty(InitiatingUser)InitiatingUser
elsestrcat(InitiatingApp, " - ", InitiatingSPID)

Stage 8: project-reorder

| project-reorder TimeGenerated, OperationName, DomainAdded, ActionInitiatedBy, InitiatingIp

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
AADOperationTypeeq
  • Add transforms: cased
DomainAddedmatch
  • onmicrosoft transforms: term
OperationNamein
  • Add unverified domain transforms: cased
  • Add verified domain transforms: cased
Resulteq
  • success transforms: cased

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
InitiatedByextend
InitiatingUserextend
InitiatingAadUserIdextend
InitiatingIpextend
InitiatingAppextend
InitiatingSPIDextend
DomainAddedextend
ActionInitiatedByextend
UserNameextend
UPNSuffixextend