Adversaries may be leveraging compromised credentials to execute arbitrary commands on Azure VMs during suspicious login windows to maintain persistence or exfiltrate data. SOC teams should proactively hunt for this behavior to identify potential lateral movement or command and control activities in their Azure Sentinel environment.
KQL Query
AzureActivity
// Isolate run command actions
| where OperationNameValue =~ "MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION"
// Confirm that the operation impacted a virtual machine
| where Authorization has "virtualMachines"
// Each runcommand operation consists of three events when successful, Started, Accepted (or Rejected), Successful (or Failed).
| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), max(CallerIpAddress), make_list(ActivityStatusValue) by CorrelationId, Authorization, Caller
// Limit to Run Command executions that Succeeded
| where list_ActivityStatusValue has_any ("Success", "Succeeded")
// Extract data from the Authorization field
| extend Authorization_d = parse_json(Authorization)
| extend Scope = Authorization_d.scope
| extend Scope_s = split(Scope, "/")
| extend Subscription = tostring(Scope_s[2])
| extend VirtualMachineName = tostring(Scope_s[-1])
| project StartTime, EndTime, Subscription, VirtualMachineName, CorrelationId, Caller, CallerIpAddress=max_CallerIpAddress
// Create a join key using the Caller (UPN)
| extend joinkey = tolower(Caller)
// Join the Run Command actions to UEBA data
| join kind = inner (
BehaviorAnalytics
// We are specifically interested in unusual logins
| where EventSource == "Azure AD" and ActivityInsights.ActionUncommonlyPerformedByUser == "True"
| project UEBAEventTime=TimeGenerated, UEBAActionType=ActionType, UserPrincipalName, UEBASourceIPLocation=SourceIPLocation, UEBAActivityInsights=ActivityInsights, UEBAUsersInsights=UsersInsights
| where isnotempty(UserPrincipalName) and isnotempty(UEBASourceIPLocation)
| extend joinkey = tolower(UserPrincipalName)
) on joinkey
// Create a window around the UEBA event times, check to see if the Run Command action was performed within them
| extend UEBAWindowStart = UEBAEventTime - 1h, UEBAWindowEnd = UEBAEventTime + 6h
| where StartTime between (UEBAWindowStart .. UEBAWindowEnd)
| project StartTime, EndTime, Subscription, VirtualMachineName, Caller, CallerIpAddress, UEBAEventTime, UEBAActionType, UEBASourceIPLocation, UEBAActivityInsights, UEBAUsersInsights
| extend AccountName = tostring(split(Caller, "@")[0]), AccountUPNSuffix = tostring(split(Caller, "@")[1])
id: 11bda520-a965-4654-9a45-d09f372f71aa
name: Azure VM Run Command operation executed during suspicious login window
description: |
'Identifies when the Azure Run Command operation is executed by a UserPrincipalName and IP Address that has resulted in a recent user entity behaviour alert.'
severity: High
requiredDataConnectors:
- connectorId: AzureActivity
dataTypes:
- AzureActivity
- connectorId: BehaviorAnalytics
dataTypes:
- BehaviorAnalytics
queryFrequency: 1d
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
- CredentialAccess
relevantTechniques:
- T1570
- T1212
query: |
AzureActivity
// Isolate run command actions
| where OperationNameValue =~ "MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION"
// Confirm that the operation impacted a virtual machine
| where Authorization has "virtualMachines"
// Each runcommand operation consists of three events when successful, Started, Accepted (or Rejected), Successful (or Failed).
| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), max(CallerIpAddress), make_list(ActivityStatusValue) by CorrelationId, Authorization, Caller
// Limit to Run Command executions that Succeeded
| where list_ActivityStatusValue has_any ("Success", "Succeeded")
// Extract data from the Authorization field
| extend Authorization_d = parse_json(Authorization)
| extend Scope = Authorization_d.scope
| extend Scope_s = split(Scope, "/")
| extend Subscription = tostring(Scope_s[2])
| extend VirtualMachineName = tostring(Scope_s[-1])
| project StartTime, EndTime, Subscription, VirtualMachineName, CorrelationId, Caller, CallerIpAddress=max_CallerIpAddress
// Create a join key using the Caller (UPN)
| extend joinkey = tolower(Caller)
// Join the Run Command actions to UEBA data
| join kind = inner (
BehaviorAnalytics
// We are specifically interested in unusual logins
| where EventSource == "Azure AD" and ActivityInsights.ActionUncommonlyPerformedByUser == "True"
| project UEBAEventTime=TimeGenerated, UEBAActionType=ActionType, UserPrincipalName, UEBASourceIPLocation=SourceIPLocation, UEBAActivityInsights=ActivityInsights, UEBAUsersInsights=UsersInsights
| where isnotempty(UserPrincipalName) and isnotempty(UEBASourceIPLocation)
| extend joinkey = tolower(UserPrincipalName)
) on joinkey
// Create a window around the UEBA event times, check to see if the Run Command action was performed within them
| extend UEBAWindowStart = UEBAEventTime - 1h, UEBAWindowEnd = UEBAEventTime + 6h
| where StartTime between (UEBAWindowStart .. UEBAWindowEnd)
| project StartTime, EndTime, Subscription, VirtualMachineName, Caller, CallerIpAddress, UEBAEventTime, UEBAActionType, UEBASourceIPLocation, UEBAActivityInsights, UEBAUsersInsights
| extend AccountName = tostring(split(Caller, "@")[0]), AccountUPNSuffix = tostring(split(Caller, "@")[1])
entityMappings:
- entityType: Account
| Sentinel Table | Notes |
|---|---|
AzureActivity | Ensure this data connector is enabled |
BehaviorAnalytics | Ensure this data connector is enabled |
Scenario: Scheduled VM Maintenance via Azure Run Command
Description: A system administrator uses Azure Run Command to execute a maintenance script on a VM during a scheduled window.
Filter/Exclusion: azure_run_command.command_name contains "maintenance_script" or azure_run_command.resource_group contains "prod-maintenance"
Scenario: Admin Task via Azure Portal
Description: An admin uses the Azure portal to run a command to configure a VM, such as updating a service or applying a configuration change.
Filter/Exclusion: azure_run_command.user_principal_name contains "[email protected]" or azure_run_command.user_agent contains "AzurePortal"
Scenario: Automated Backup Job Execution
Description: A backup tool like Veeam or Azure Backup runs a Run Command to trigger a backup process on a VM during a known backup window.
Filter/Exclusion: azure_run_command.command_name contains "backup" or azure_run_command.resource_group contains "backup-vm"
Scenario: Patching via Azure Automation
Description: Azure Automation runs a Run Command to apply OS patches or updates on multiple VMs during a scheduled patching window.
Filter/Exclusion: azure_run_command.command_name contains "patch" or azure_run_command.command_id contains "patching-job-123"
Scenario: User-Initiated Configuration Change
Description: A user with elevated privileges (e.g., domain\it-support) initiates a Run Command to configure a VM, such as setting up a new service or updating a configuration file.
Filter/Exclusion: azure_run_command.user_principal_name contains "[email protected]" or azure_run_command.command_name contains "config-update"