Detection rules › Kusto

Azure Diagnostic settings removed from a resource

Severity
medium
Time window
2h
Group by
CorrelationId, MonitoredResourcePath, OperationNameValue, _ResourceId
Author
KennethMLdk
Source
github.com/Azure/Azure-Sentinel

This query looks for diagnostic settings that are removed from a resource. This could indicate an attacker or malicious internal trying to evade detection before malicious act is performed. If the diagnostic settings are being deleted as part of a parent resource deletion, the event is ignores.

MITRE ATT&CK coverage

Telemetry coverage

Rule body

id: 6e95aef3-a1e0-4063-8e74-cd59aa59f245
name: Azure Diagnostic settings removed from a resource
description: |
  'This query looks for diagnostic settings that are removed from a resource.
  This could indicate an attacker or malicious internal trying to evade detection before malicious act is performed.
  If the diagnostic settings are being deleted as part of a parent resource deletion, the event is ignores.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActivity
    dataTypes:
      - AzureActivity
queryFrequency: 1h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562.008
query: |
  AzureActivity
  | where OperationNameValue =~ "MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/DELETE"
  | summarize
      TimeGenerated = arg_max(TimeGenerated, Properties),
      ActivityStatusValue = make_set(ActivityStatusValue, 5),
      take_any(Caller, CallerIpAddress, OperationName, ResourceGroup, Resource)
      by CorrelationId, _ResourceId, OperationNameValue
  | extend ResourceHierarchy = split(_ResourceId, "/")
  | extend MonitoredResourcePath = strcat_array(array_slice(ResourceHierarchy, 0, array_length(ResourceHierarchy)-5), "/")
  | join kind=leftanti (
      AzureActivity
      | where OperationNameValue !~ "MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/DELETE" and OperationNameValue endswith "/DELETE" and ActivityStatusValue has_any ("Success", "Succeeded")
      | project _ResourceId
  ) on $left.MonitoredResourcePath == $right._ResourceId
  | extend
      Name = iif(Caller has "@", tostring(split(Caller, "@")[0]), ""),
      UPNSuffix = iif(Caller has "@", tostring(split(Caller, "@")[1]), ""),
      AadUserId = iif(Caller has "@", "", Caller)
  | project TimeGenerated, Caller, CallerIpAddress, OperationNameValue, OperationName, ActivityStatusValue, ResourceGroup, MonitoredResourcePath, Resource, Properties, Name, UPNSuffix, AadUserId, _ResourceId, CorrelationId
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Caller
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: AadUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: CallerIpAddress
version: 1.0.3
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: KennethMLdk
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Platform" ]

Stages and Predicates

Stage 1: source

AzureActivity

Stage 2: where

| where OperationNameValue =~ "MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/DELETE"

Stage 3: summarize

| summarize
    TimeGenerated = arg_max(TimeGenerated, Properties),
    ActivityStatusValue = make_set(ActivityStatusValue, 5),
    take_any(Caller, CallerIpAddress, OperationName, ResourceGroup, Resource)
    by CorrelationId, _ResourceId, OperationNameValue

Stage 4: extend

| extend ResourceHierarchy = split(_ResourceId, "/")

Stage 5: extend

| extend MonitoredResourcePath = strcat_array(array_slice(ResourceHierarchy, 0, array_length(ResourceHierarchy)-5), "/")

Stage 6: join (negated)

| join kind=leftanti (
    AzureActivity
    | where OperationNameValue !~ "MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/DELETE" and OperationNameValue endswith "/DELETE" and ActivityStatusValue has_any ("Success", "Succeeded")
    | project _ResourceId
) on $left.MonitoredResourcePath == $right._ResourceId

Stage 7: extend

| extend
    Name = iif(Caller has "@", tostring(split(Caller, "@")[0]), ""),
    UPNSuffix = iif(Caller has "@", tostring(split(Caller, "@")[1]), ""),
    AadUserId = iif(Caller has "@", "", Caller)

Stage 8: project

| project TimeGenerated, Caller, CallerIpAddress, OperationNameValue, OperationName, ActivityStatusValue, ResourceGroup, MonitoredResourcePath, Resource, Properties, Name, UPNSuffix, AadUserId, _ResourceId, CorrelationId

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
AadUserIdproject
ActivityStatusValueproject
Callerproject
CallerIpAddressproject
CorrelationIdproject
MonitoredResourcePathproject
Nameproject
OperationNameproject
OperationNameValueproject
Propertiesproject
Resourceproject
ResourceGroupproject
TimeGeneratedproject
UPNSuffixproject
_ResourceIdproject