Detection rules › Kusto

Rare subscription-level operations in Azure

Status
available
Severity
low
Time window
1d
Group by
Caller, CallerIpAddress, OperationNameValue
Source
github.com/Azure/Azure-Sentinel

This query looks for a few sensitive subscription-level events based on Azure Activity Logs. For example, this monitors for the operation name 'Create or Update Snapshot', which is used for creating backups but could be misused by attackers to dump hashes or extract sensitive information from the disk.

MITRE ATT&CK coverage

TacticTechniques
PersistenceT1098 Account Manipulation
Credential AccessT1003 OS Credential Dumping

Event coverage

Rule body kusto

id: 23de46ea-c425-4a77-b456-511ae4855d69
name: Rare subscription-level operations in Azure
description: |
  'This query looks for a few sensitive subscription-level events based on Azure Activity Logs. For example, this monitors for the operation name 'Create or Update Snapshot', which is used for creating backups but could be misused by attackers to dump hashes or extract sensitive information from the disk.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: AzureActivity
    dataTypes:
      - AzureActivity
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - Persistence
relevantTechniques:
  - T1003
  - T1098
query: |
  let starttime = 14d;
  let endtime = 1d;
  // The number of operations above which an IP address is considered an unusual source of role assignment operations
  let alertOperationThreshold = 5;
  // Add or remove operation names below as per your requirements. For operations lists, please refer to https://learn.microsoft.com/en-us/Azure/role-based-access-control/resource-provider-operations#all
  let SensitiveOperationList =  dynamic(["microsoft.compute/snapshots/write", "microsoft.network/networksecuritygroups/write", "microsoft.storage/storageaccounts/listkeys/action"]);
  let SensitiveActivity = AzureActivity
  | where OperationNameValue in~ (SensitiveOperationList) or OperationNameValue hassuffix "listkeys/action"
  | where ActivityStatusValue =~ "Success";
  SensitiveActivity
  | where TimeGenerated between (ago(starttime) .. ago(endtime))
  | summarize count() by CallerIpAddress, Caller, OperationNameValue, bin(TimeGenerated,1d)
  | where count_ >= alertOperationThreshold
  // Returns all the records from the right side that don't have matches from the left
  | join kind = rightanti (
  SensitiveActivity
  | where TimeGenerated >= ago(endtime)
  | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ActivityTimeStamp = make_list(TimeGenerated), ActivityStatusValue = make_list(ActivityStatusValue), CorrelationIds = make_list(CorrelationId), ResourceGroups = make_list(ResourceGroup), ResourceIds = make_list(_ResourceId), ActivityCountByCallerIPAddress = count()
  by CallerIpAddress, Caller, OperationNameValue
  | where ActivityCountByCallerIPAddress >= alertOperationThreshold
  ) on CallerIpAddress, Caller, OperationNameValue
  | extend Name = tostring(split(Caller,'@',0)[0]), UPNSuffix = tostring(split(Caller,'@',1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Caller
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: CallerIpAddress
version: 2.0.3
kind: Scheduled

Stages and Predicates

Parameters

let starttime = 14d;
let endtime = 1d;
let alertOperationThreshold = 5;
let SensitiveOperationList = dynamic(["microsoft.compute/snapshots/write", "microsoft.network/networksecuritygroups/write", "microsoft.storage/storageaccounts/listkeys/action"]);

The stages below define let SensitiveActivity (the rule's main pipeline source).

Stage 1: source

AzureActivity

Stage 2: where

| where OperationNameValue in~ (SensitiveOperationList) or OperationNameValue hassuffix "listkeys/action"

Stage 3: where

| where ActivityStatusValue =~ "Success"

The stages below run on SensitiveActivity (the outer pipeline).

Stage 4: where

SensitiveActivity
| where TimeGenerated between (ago(starttime) .. ago(endtime))

Stage 5: summarize

| summarize count() by CallerIpAddress, Caller, OperationNameValue, bin(TimeGenerated,1d)

Stage 6: where

| where count_ >= alertOperationThreshold

Stage 7: join (negated)

| join kind = rightanti (
SensitiveActivity
| where TimeGenerated >= ago(endtime)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ActivityTimeStamp = make_list(TimeGenerated), ActivityStatusValue = make_list(ActivityStatusValue), CorrelationIds = make_list(CorrelationId), ResourceGroups = make_list(ResourceGroup), ResourceIds = make_list(_ResourceId), ActivityCountByCallerIPAddress = count()
by CallerIpAddress, Caller, OperationNameValue
| where ActivityCountByCallerIPAddress >= alertOperationThreshold
) on CallerIpAddress, Caller, OperationNameValue

Stage 8: extend

| extend Name = tostring(split(Caller,'@',0)[0]), UPNSuffix = tostring(split(Caller,'@',1)[0])

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
OperationNameValueends_withlistkeys/action
OperationNameValueinmicrosoft.compute/snapshots/write, microsoft.network/networksecuritygroups/write, microsoft.storage/storageaccounts/listkeys/action
ActivityCountByCallerIPAddressge5
ActivityStatusValueeqSuccess

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
ActivityStatusValueeq
  • Success
OperationNameValueends_with
  • listkeys/action
OperationNameValuein
  • microsoft.compute/snapshots/write
  • microsoft.network/networksecuritygroups/write
  • microsoft.storage/storageaccounts/listkeys/action
count_ge
  • 5 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
Callersummarize
CallerIpAddresssummarize
OperationNameValuesummarize
Nameextend
UPNSuffixextend