Detection rules › Kusto

Workspace deletion activity from an infected device

Severity
medium
Time window
14d
Group by
AadTenantId, AadUserId, AccountObjectId, AccountTenantId, IpAddress, UserAccount
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Initial Access
Impact

Telemetry coverage

Rule body

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 =
if(CompromisedEntity == "N/A" or isempty(CompromisedEntity))UserAccount
elseCompromisedEntity

Stage 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

These rows show field, operator, and value matches.

FieldKindValuesSearch
AccountDisplayNameis_not_null
  • (no value, null check)
field:"AccountDisplayName" kind:is_not_null
ActivityStatusValuematch
  • Succeeded transforms: term
  • Success transforms: term
field:"ActivityStatusValue" kind:match
AlertNameeq
  • Sign-in from an infected device
field:"AlertName" kind:eq value:"Sign-in from an infected device"
OperationNameValuematch
  • /workspaces/computes/delete transforms: term
  • workspaces/delete transforms: term
field:"azure_ad::operation_name_value" kind:match
ProductNameeq
  • Azure Active Directory Identity Protection
field:"ProductName" kind:eq value:"Azure Active Directory Identity Protection"
Typeeq
  • account
  • ip
field:"Type" kind:eq
UserAccountis_not_null
  • (no value, null check)
field:"UserAccount" kind:is_not_null

Output fields

These fields are emitted when the rule matches.

FieldSource
AlertNameproject
AlertSeverityproject
CompromisedEntityproject
IpAddressproject
TimeGeneratedproject
UserAccountproject
UserNameproject
AccountNameextend
AccountUPNSuffixextend