Detection rules › Kusto
Workspace deletion activity from an infected device
'This query will alert on any sign-ins from devices infected with malware in correlation with workspace deletion activity. Attackers may attempt to delete workspaces containing compute instances after successful compromise to cause service unavailability to regular business operation.'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1078 Valid Accounts |
| Impact | T1489 Service Stop |
Rule body kusto
id: a5b3429d-f1da-42b9-883c-327ecb7b91ff
name: Workspace deletion activity from an infected device
description: |
'This query will alert on any sign-ins from devices infected with malware in correlation with workspace deletion activity.
Attackers may attempt to delete workspaces containing compute instances after successful compromise to cause service unavailability to regular business operation.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectoryIdentityProtection
dataTypes:
- SecurityAlert (IPC)
- connectorId: AzureActivity
dataTypes:
- AzureActivity
- connectorId: BehaviorAnalytics
dataTypes:
- IdentityInfo
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Impact
relevantTechniques:
- T1078
- T1489
query: |
SecurityAlert
| where TimeGenerated > ago(1d)
| where ProductName == "Azure Active Directory Identity Protection"
| where AlertName == "Sign-in from an infected device"
| mv-apply EntityAccount=todynamic(Entities) on
(
where EntityAccount.Type == "account"
| extend AadTenantId = tostring(EntityAccount.AadTenantId), AadUserId = tostring(EntityAccount.AadUserId)
)
| mv-apply EntityIp=todynamic(Entities) on
(
where EntityIp.Type == "ip"
| extend IpAddress = tostring(EntityIp.Address)
)
| join kind=inner (
IdentityInfo
| distinct AccountTenantId, AccountObjectId, AccountUPN, AccountDisplayName
| extend UserAccount = AccountUPN
| extend UserName = AccountDisplayName
| where isnotempty(AccountDisplayName) and isnotempty(UserAccount)
| project AccountTenantId, AccountObjectId, UserAccount, UserName
)
on
$left.AadTenantId == $right.AccountTenantId,
$left.AadUserId == $right.AccountObjectId
| extend CompromisedEntity = iff(CompromisedEntity == "N/A" or isempty(CompromisedEntity), UserAccount, CompromisedEntity)
| project AlertName, AlertSeverity, CompromisedEntity, UserAccount, IpAddress, TimeGenerated, UserName
| join kind=inner
(
AzureActivity
| where OperationNameValue has_any ("/workspaces/computes/delete", "workspaces/delete")
| where ActivityStatusValue has_any ("Succeeded", "Success")
| project TimeGenerated, ResourceProviderValue, _ResourceId, SubscriptionId, UserAccount=Caller, IpAddress=CallerIpAddress, CorrelationId, OperationId, ResourceGroup, TenantId
) on IpAddress, UserAccount
| extend AccountName = tostring(split(UserAccount, "@")[0]), AccountUPNSuffix = tostring(split(UserAccount, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserAccount
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IpAddress
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: _ResourceId
version: 1.0.8
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Threat Protection", "Platform" ]
Stages and Predicates
Stage 1: source
SecurityAlert
Stage 2: where
| where TimeGenerated > ago(1d)
Stage 3: where
| where ProductName == "Azure Active Directory Identity Protection"
Stage 4: where
| where AlertName == "Sign-in from an infected device"
Stage 5: kusto:mv-apply
| mv-apply EntityAccount=todynamic(Entities) on
(
where EntityAccount.Type == "account"
| extend AadTenantId = tostring(EntityAccount.AadTenantId), AadUserId = tostring(EntityAccount.AadUserId)
)
Stage 6: kusto:mv-apply
| mv-apply EntityIp=todynamic(Entities) on
(
where EntityIp.Type == "ip"
| extend IpAddress = tostring(EntityIp.Address)
)
Stage 7: join
| join kind=inner (
IdentityInfo
| distinct AccountTenantId, AccountObjectId, AccountUPN, AccountDisplayName
| extend UserAccount = AccountUPN
| extend UserName = AccountDisplayName
| where isnotempty(AccountDisplayName) and isnotempty(UserAccount)
| project AccountTenantId, AccountObjectId, UserAccount, UserName
)
on
$left.AadTenantId == $right.AccountTenantId,
$left.AadUserId == $right.AccountObjectId
Stage 8: extend
| extend CompromisedEntity = iff(CompromisedEntity == "N/A" or isempty(CompromisedEntity), UserAccount, CompromisedEntity)
CompromisedEntity =(CompromisedEntity == "N/A" or isempty(CompromisedEntity))UserAccountCompromisedEntityStage 9: project
| project AlertName, AlertSeverity, CompromisedEntity, UserAccount, IpAddress, TimeGenerated, UserName
Stage 10: join
| join kind=inner
(
AzureActivity
| where OperationNameValue has_any ("/workspaces/computes/delete", "workspaces/delete")
| where ActivityStatusValue has_any ("Succeeded", "Success")
| project TimeGenerated, ResourceProviderValue, _ResourceId, SubscriptionId, UserAccount=Caller, IpAddress=CallerIpAddress, CorrelationId, OperationId, ResourceGroup, TenantId
) on IpAddress, UserAccount
Stage 11: extend
| extend AccountName = tostring(split(UserAccount, "@")[0]), AccountUPNSuffix = tostring(split(UserAccount, "@")[1])
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 |
|---|---|---|
AccountDisplayName | is_not_null | |
ActivityStatusValue | match |
|
AlertName | eq |
|
OperationNameValue | match |
|
ProductName | eq |
|
Type | eq |
|
UserAccount | 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 |
|---|---|
AlertName | project |
AlertSeverity | project |
CompromisedEntity | project |
IpAddress | project |
TimeGenerated | project |
UserAccount | project |
UserName | project |
AccountName | extend |
AccountUPNSuffix | extend |