Detection rules › Kusto
Subscription moved to another tenant
This detection uses AzureActivity logs (Security category) to identify when a subscription is moved to another tenant. A threat actor may move a subscription into their own tenant to circumvent local resource deployment and logging policies. Once moved, threat actors may deploy resources and perform malicious activities such as crypto mining. This is a technique known as "subscription hijacking". More information can be found here: https://techcommunity.microsoft.com/t5/microsoft-365-defender-blog/hunt-for-compromised-azure-subscriptions-using-microsoft/ba-p/3607121
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Impact | T1496 Resource Hijacking |
Event coverage
Rule body kusto
id: 48c026d8-7f36-4a95-9568-6f1420d66e37
kind: Scheduled
name: Subscription moved to another tenant
description: |
'This detection uses AzureActivity logs (Security category) to identify when a subscription is moved to another tenant.
A threat actor may move a subscription into their own tenant to circumvent local resource deployment and logging policies.
Once moved, threat actors may deploy resources and perform malicious activities such as crypto mining.
This is a technique known as "subscription hijacking". More information can be found here: https://techcommunity.microsoft.com/t5/microsoft-365-defender-blog/hunt-for-compromised-azure-subscriptions-using-microsoft/ba-p/3607121'
severity: Low
requiredDataConnectors:
- connectorId: AzureActivity
dataTypes:
- AzureActivity
queryPeriod: 20m
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1496
query: |
let queryFrequency = 5m;
let eventCapture = "moved from tenant ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}) to tenant ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})";
AzureActivity
| where ingestion_time() > ago(queryFrequency)
| where CategoryValue =~ "Security"
| where OperationNameValue =~ "Microsoft.Subscription/updateTenant/action"
| extend Properties_d = coalesce(parse_json(Properties), Properties_d)
| where isnotempty(Properties_d)
| extend Summary = tostring(Properties_d.message)
| extend EventCapture = extract_all(eventCapture, Summary)
| extend SourceTenantId = iff(isnotempty(EventCapture), EventCapture[0][0], "")
| extend DestinationTenantId = iff(isnotempty(EventCapture), EventCapture[0][1], "")
| extend
Name = split(Caller, "@", 0)[0],
UPNSuffix = split(Caller, "@", 1)[0]
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: _ResourceId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Caller
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
customDetails:
DestinationTenantId: DestinationTenantId
SourceTenantId: SourceTenantId
alertDetailsOverride:
alertDescriptionFormat: |
The user {{Caller}} moved a subscription:
{{Summary}}
If this was not expected, it may indicate a subscription hijacking event.
alertDisplayNameFormat: |
Subscription {{SubscriptionId}} changed tenants
version: 1.0.1
Stages and Predicates
Parameters
let queryFrequency = 5m;
Let binding: eventCapture
let eventCapture = "moved from tenant ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}) to tenant ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})";
Stage 1: source
AzureActivity
Stage 2: where
| where ingestion_time() > ago(queryFrequency)
Stage 3: where
| where CategoryValue =~ "Security"
Stage 4: where
| where OperationNameValue =~ "Microsoft.Subscription/updateTenant/action"
Stage 5: extend
| extend Properties_d = coalesce(parse_json(Properties), Properties_d)
Stage 6: where
| where isnotempty(Properties_d)
Stage 7: extend (5 consecutive steps)
| extend Summary = tostring(Properties_d.message)
| extend EventCapture = extract_all(eventCapture, Summary)
| extend SourceTenantId = iff(isnotempty(EventCapture), EventCapture[0][0], "")
| extend DestinationTenantId = iff(isnotempty(EventCapture), EventCapture[0][1], "")
| extend
Name = split(Caller, "@", 0)[0],
UPNSuffix = split(Caller, "@", 1)[0]
References eventCapture (defined above).
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.
| Field | Kind | Values |
|---|---|---|
CategoryValue | eq |
|
OperationNameValue | eq |
|
Properties_d | is_not_null |
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.
| Field | Source |
|---|---|
Properties_d | extend |
Summary | extend |
EventCapture | extend |
SourceTenantId | extend |
DestinationTenantId | extend |
Name | extend |
UPNSuffix | extend |