Cloud Administration Command
This detection identifies adversaries abusing cloud-native management services — such as AWS Systems Manager (SSM) Run Command, Azure RunCommand, and Azure Automation Runbooks — to remotely execute commands inside virtual machines. Because these mechanisms use legitimate, pre-installed VM agents (SSM Agent, Azure VM Agent), execution is indistinguishable from authorized administrative activity at the OS level. The detection focuses on the cloud control plane: auditing who invoked the run-command API, from what identity/IP, against which VMs, and whether the invocation pattern deviates from baseline administrative behavior. High-severity APT29/Nobelium tradecraft has leveraged Azure Run Command and Admin-on-Behalf-of (AOBO) post-compromise to execute code on tenant VMs without touching traditional lateral movement paths.
What is T1651 Cloud Administration Command?
Cloud Administration Command (T1651) maps to the Execution tactic — the adversary is trying to run malicious code in MITRE ATT&CK.
This page provides production-ready detection logic for Cloud Administration Command, covering the data sources and telemetry it touches: Azure Monitor, Azure Activity Logs, Microsoft Sentinel. The queries below are rated high severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.
MITRE ATT&CK
- Tactic
- Execution
- Technique
- T1651 Cloud Administration Command
- Canonical reference
- https://attack.mitre.org/techniques/T1651/
// Detection 1: Azure RunCommand invocations via AzureActivity
let SuspiciousRunCommandOps = AzureActivity
| where TimeGenerated >= ago(24h)
| where OperationNameValue has_any (
"MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION",
"Microsoft.Compute/virtualMachines/runCommand/action"
)
| where ActivityStatusValue in ("Success", "Accepted", "Started")
| extend CallerIdentity = Caller
| extend VMName = tostring(split(ResourceId, "/")[8])
| extend ResourceGroupName = ResourceGroup
| extend SourceIP = CallerIpAddress
| project
TimeGenerated,
CallerIdentity,
SourceIP,
VMName,
ResourceGroupName,
SubscriptionId,
OperationNameValue,
ActivityStatusValue,
Properties
;
// Detection 2: Azure Automation Runbook execution
let RunbookOps = AzureActivity
| where TimeGenerated >= ago(24h)
| where OperationNameValue has_any (
"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/JOBS/WRITE",
"Microsoft.Automation/automationAccounts/jobs/write",
"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/DRAFT/TESTJOB/WRITE"
)
| where ActivityStatusValue in ("Success", "Accepted")
| extend CallerIdentity = Caller
| extend AutomationAccount = tostring(split(ResourceId, "/")[8])
| project
TimeGenerated,
CallerIdentity,
SourceIP = CallerIpAddress,
AutomationAccount,
ResourceGroup,
SubscriptionId,
OperationNameValue,
ActivityStatusValue
;
SuspiciousRunCommandOps
| union RunbookOps
| order by TimeGenerated desc Detects Azure RunCommand and Azure Automation Runbook invocations via the AzureActivity log. Monitors for the specific operation names used when an identity (user, service principal, or delegated admin) calls the RunCommand API on a VM or submits an Automation job. Alerts on both successful and in-progress invocations to capture the earliest signal. A secondary union covers Runbook job creation which can achieve the same code execution outcome as RunCommand.
Data Sources
Required Tables
False Positives
- Legitimate IT operations teams using Azure RunCommand for patching, configuration management, or troubleshooting via approved change tickets
- Azure Automation Runbooks configured for scheduled maintenance tasks such as VM shutdowns, certificate rotation, or log collection
- Cloud management platforms (Ansible Tower, HashiCorp Terraform, Azure Arc) that use RunCommand as part of infrastructure-as-code pipelines
- Security tooling or EDR agents that use RunCommand to push policy updates or perform remediation actions on endpoints
- Azure Monitor or Log Analytics agent extensions that periodically use VM management APIs for health reporting
Sigma rule & cross-platform mapping
The detection logic for Cloud Administration Command (T1651) above is provided in a vendor-neutral
form so you can deploy it on any SIEM. The same logic is shipped here as native
KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the
following logsource:
logsource:
product: azure Browse the community-maintained Sigma rules for this technique:
Platform-specific guides for T1651
References (6)
- https://attack.mitre.org/techniques/T1651/
- https://docs.microsoft.com/en-us/azure/virtual-machines/run-command-overview
- https://docs.aws.amazon.com/systems-manager/latest/userguide/execute-remote-commands.html
- https://www.microsoft.com/security/blog/2021/10/25/nobelium-targeting-delegated-administrative-privileges-to-facilitate-broader-attacks/
- https://o365blog.com/post/run-command/
- https://github.com/RhinoSecurityLabs/pacu
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Azure RunCommand - Execute PowerShell via Azure CLI
Expected signal: AzureActivity log entry with OperationName 'Microsoft.Compute/virtualMachines/runCommand/action', ActivityStatus 'Succeeded', and Caller set to the authenticated user's UPN or service principal object ID. On the VM: SecurityEvent 4688 showing powershell.exe spawned by WindowsAzureGuestAgent.exe.
- Test 2AWS SSM Run Command - Execute Shell Script on EC2 Instance
Expected signal: AWS CloudTrail event with eventName 'SendCommand', eventSource 'ssm.amazonaws.com', requestParameters containing documentName 'AWS-RunShellScript' and target instanceId. On the EC2 instance: /var/log/amazon/ssm/amazon-ssm-agent.log entries showing command receipt and execution.
- Test 3Azure Automation Runbook - Execute Commands via Automation Account
Expected signal: AzureActivity log entries with OperationName 'Microsoft.Automation/automationAccounts/runbooks/write', 'Microsoft.Automation/automationAccounts/jobs/write', and 'Microsoft.Automation/automationAccounts/jobs/read'. Azure Automation job logs in the portal showing execution output.
Response Playbook
Triage
- Step 1: Identify the calling identity (Caller field in AzureActivity). Determine if it is a user UPN, managed identity object ID, or service principal. Check Azure AD sign-in logs (SigninLogs) for the same identity in the same time window — did the account authenticate from an unusual IP, country, or device?
- Step 2: Review the Properties field of the AzureActivity record to extract the RunCommand script content if available. Azure stores the script payload in the Properties.requestBody field for audit purposes. Decode any Base64-encoded content immediately.
- Step 3: Check whether the caller account is a delegated administrator or CSP partner account using Azure AD Privileged Identity Management (PIM) logs or AuditLogs with category 'RoleManagement'. APT29 AOBO abuse involves delegated admin accounts that are often not in the victim tenant's own user directory.
- Step 4: Determine the scope of VM targeting. Query AzureActivity for all RunCommand invocations by this caller across all subscriptions in the past 72 hours. Mass targeting (>5 VMs) is a critical escalation indicator.
- Step 5: Correlate the RunCommand event timestamp with VM-level process execution logs. On Windows VMs, check SecurityEvent (Event ID 4688) or DeviceProcessEvents for cmd.exe, powershell.exe, or bash processes spawned by the Azure VM Agent (WindowsAzureGuestAgent.exe) around the same time.
- Step 6: Check if the invocation originated from within Azure Cloud Shell, the Azure Portal, or a CLI tool (user-agent in Properties). External API calls or unfamiliar user-agents may indicate automated tooling like Pacu or AADInternals.
Containment
- Immediately revoke or disable the compromised identity's Azure RBAC assignments at the subscription level using: az role assignment delete --assignee <object-id> --scope /subscriptions/<subscription-id>
- If a service principal is compromised, rotate its credentials immediately: az ad sp credential reset --id <sp-object-id> and revoke all existing client secrets and certificates
- For delegated administrator abuse, contact Microsoft Partner Center support and request emergency revocation of the delegated admin relationship. Remove the foreign tenant from Grantor Management under Azure AD External Identities
- Apply an Azure Policy deny effect for the 'Microsoft.Compute/virtualMachines/runCommand/action' operation on affected subscriptions while the investigation is in progress
- Isolate affected VMs from the network using Network Security Groups (NSGs) to block outbound internet traffic while preserving inbound management access for forensic collection
- If the attacker used RunCommand to install a backdoor or persistence mechanism, do NOT simply restart the VM — snapshot the VM disk first for forensic analysis before any remediation
Evidence Collection
- Export the full AzureActivity log for the affected subscription for the past 30 days: use Azure Monitor Log Analytics export or the Azure Activity Log diagnostic settings to an Event Hub, then archive to Storage Account
- Retrieve the exact RunCommand script content from the AzureActivity Properties field. If the script is truncated, query the Azure Compute RunCommand execution history using: az vm run-command show --resource-group <rg> --vm-name <vm> --run-command-id RunPowerShellScript
- Collect the Azure VM Agent log from the affected VM: C:\WindowsAzure\Logs\WaAppAgent.log (Windows) or /var/log/waagent.log (Linux). This log records every command dispatched to the agent including timestamps and execution status
- On Windows VMs, export PowerShell ScriptBlock logs (Event ID 4104 from Microsoft-Windows-PowerShell/Operational) for the execution window — RunCommand scripts appear here even if event log tampering occurred later
- Capture the Azure AD sign-in log for the offending identity: AuditLogs and SigninLogs filtered to the caller's objectId for the past 30 days. Export as CSV via Azure AD portal or via Microsoft Graph API
- If AWS SSM RunCommand is involved: export CloudTrail logs for the SendCommand, ListCommandInvocations, and GetCommandInvocation API calls. Note: SSM command documents and output are also stored in S3 if configured
Escalation Criteria
- ! Escalate immediately if the RunCommand script content contains credential harvesting commands (e.g., Invoke-Mimikatz, procdump lsass, /etc/shadow reads), encoded payloads, or downloads from external URLs
- ! Escalate if the caller identity is a delegated admin or service provider account — this indicates potential supply chain compromise or CSP account takeover (APT29 AOBO pattern)
- ! Escalate if RunCommand was invoked against 3 or more VMs within a 30-minute window — this indicates a mass execution campaign and possible ransomware deployment or cryptominer installation
- ! Escalate if the VM agent logs show subsequent outbound connections to non-Azure IP addresses following the RunCommand execution — indicates successful backdoor or C2 installation
- ! Escalate if Azure AD PIM logs show just-in-time role activation for Owner or Contributor immediately before the RunCommand invocation, especially outside business hours or from a new device
- ! Escalate if the same technique is observed across multiple Azure tenants (possible cross-tenant attack via compromised CSP)
Investigation Guide
Forensic Artifacts
- >
Azure Activity Log entries with OperationName 'Microsoft.Compute/virtualMachines/runCommand/action' - >
Azure VM Agent log: C:\WindowsAzure\Logs\WaAppAgent.log (Windows) or /var/log/waagent.log (Linux) - >
Windows Event ID 4688 (Process Creation) for cmd.exe or powershell.exe spawned by WindowsAzureGuestAgent.exe - >
PowerShell ScriptBlock logs (Event ID 4104) in Microsoft-Windows-PowerShell/Operational channel - >
AWS CloudTrail logs: SendCommand, ListCommandInvocations, GetCommandInvocation API calls - >
AWS SSM Run Command output stored in S3 bucket (if output logging is configured) - >
Azure Automation job history including runbook content, parameters, and output in the Automation Account portal - >
Network flow logs (NSG Flow Logs or VPC Flow Logs) for outbound connections initiated shortly after RunCommand execution - >
/var/log/cloud-init.log and /var/log/syslog on Linux VMs for agent-dispatched command execution - >
Azure AD audit logs showing role assignments or PIM activations preceding the RunCommand invocation
Tuning Guidance
Begin by building an allowlist of known-legitimate RunCommand callers (service principal object IDs for deployment pipelines, PIM-activated admin roles used during maintenance windows) and suppressing them from alerting. Add time-of-day filters to reduce noise during scheduled maintenance windows. For AWS, scope the detection to specific high-value SSM document names (AWS-RunShellScript, AWS-RunPowerShellScript) and exclude AWS Systems Manager Agent self-update documents. To reduce false positives from Azure Automation, create a lookup table of known Runbook job caller service principals and exclude them. The highest-fidelity signal is a RunCommand invocation from a delegated admin or cross-tenant identity — always alert on these regardless of other context. Consider adding a risk score multiplier when RunCommand is invoked outside business hours or from a geographically anomalous IP for the calling identity.
Hunting Queries
Hunts for RunCommand invocations from identities that have no historical usage of this API in the past 90 days. First-time RunCommand callers are high-fidelity indicators of either credential compromise or a new attack path being established.
// Hunt: Identify RunCommand invocations from first-time callers
let KnownAdmins = AzureActivity
| where TimeGenerated between(ago(90d)..ago(7d))
| where OperationNameValue has "RUNCOMMAND"
| summarize KnownCallers=make_set(Caller);
AzureActivity
| where TimeGenerated >= ago(7d)
| where OperationNameValue has "RUNCOMMAND"
| where ActivityStatusValue == "Success"
| extend CallerIdentity = Caller
| where CallerIdentity !in (toscalar(KnownAdmins))
| project TimeGenerated, CallerIdentity, CallerIpAddress, ResourceGroup, Resource, OperationNameValue, Properties
| order by TimeGenerated desc index=* sourcetype="azure:activity" operationName="Microsoft.Compute/virtualMachines/runCommand/action" resultType="Success"
| eval caller=coalesce(caller, properties.caller)
| eventstats count AS total_invocations BY caller
| eventstats dc(caller) AS unique_callers
| eval baseline_end=relative_time(now(), "-7d")
| eval baseline_start=relative_time(now(), "-90d")
| search _time >= baseline_end
| join type=left caller [
search index=* sourcetype="azure:activity" operationName="Microsoft.Compute/virtualMachines/runCommand/action" _time>=baseline_start _time<baseline_end
| stats count BY caller
| rename count AS historical_count
]
| where isnull(historical_count) OR historical_count=0
| table _time, caller, callerIpAddress, resourceGroup, resource, total_invocations
| sort - total_invocations Correlates Azure RunCommand control plane events with outbound network connections from the targeted VM within a 30-minute window. Identifies cases where RunCommand was used to implant a backdoor or C2 agent by linking the cloud API call to the resulting network telemetry.
// Hunt: Correlate Azure RunCommand with subsequent outbound network connections from targeted VMs
let RunCommandEvents = AzureActivity
| where TimeGenerated >= ago(24h)
| where OperationNameValue has "RUNCOMMAND"
| where ActivityStatusValue == "Success"
| extend VMResourceId = tolower(ResourceId)
| project RunCommandTime=TimeGenerated, VMResourceId, CallerIdentity=Caller, CallerIP=CallerIpAddress;
let NetworkEvents = DeviceNetworkEvents
| where TimeGenerated >= ago(24h)
| where ActionType == "ConnectionSuccess"
| where RemoteIPType != "Private"
| project NetworkTime=TimeGenerated, DeviceName, RemoteIP, RemotePort, InitiatingProcessFileName;
RunCommandEvents
| join kind=inner (
NetworkEvents
) on $left.VMResourceId contains $right.DeviceName
| where NetworkTime between (RunCommandTime .. datetime_add('minute', 30, RunCommandTime))
| project RunCommandTime, NetworkTime, DeviceName, CallerIdentity, CallerIP, RemoteIP, RemotePort, InitiatingProcessFileName
| order by RunCommandTime desc index=* sourcetype="azure:activity" operationName="Microsoft.Compute/virtualMachines/runCommand/action" resultType="Success"
| eval vm_name=mvindex(split(resourceId, "/"), 8)
| eval cmd_time=_time
| eval cmd_time_plus30m=cmd_time+1800
| table cmd_time, cmd_time_plus30m, vm_name, caller, callerIpAddress
| join type=inner vm_name [
search index=* sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=3 Initiated=true
| eval dest_ip=DestinationIp
| eval conn_time=_time
| eval computer_lower=lower(Computer)
| table conn_time, computer_lower, dest_ip, DestinationPort, Image
| rename computer_lower AS vm_name
]
| where conn_time >= cmd_time AND conn_time <= cmd_time_plus30m
| table cmd_time, vm_name, caller, callerIpAddress, conn_time, dest_ip, DestinationPort, Image
| sort - cmd_time Hunts for AWS SSM RunCommand and Systems Manager Automation executions in CloudTrail. Specifically targets AWS-RunShellScript and AWS-RunPowerShellScript document executions which provide arbitrary command execution on EC2 instances, and surfaces callers targeting multiple instances which is consistent with lateral movement or mass deployment campaigns.
// Hunt: AWS SSM RunCommand via CloudTrail (if AWS CloudTrail connector is enabled)
AWSCloudTrail
| where TimeGenerated >= ago(24h)
| where EventName in ("SendCommand", "SendAutomationExecution")
| where ErrorCode == ""
| extend TargetInstances = tostring(RequestParameters.instanceIds)
| extend DocumentName = tostring(RequestParameters.documentName)
| extend CallerARN = tostring(UserIdentity.arn)
| extend CallerType = tostring(UserIdentity.type)
| extend SourceIP = SourceIpAddress
| where DocumentName in ("AWS-RunShellScript", "AWS-RunPowerShellScript", "AWS-RunRemoteScript") or isnotempty(DocumentName)
| project TimeGenerated, CallerARN, CallerType, SourceIP, TargetInstances, DocumentName, RequestParameters, AWSRegion
| order by TimeGenerated desc index=* sourcetype="aws:cloudtrail" eventName IN ("SendCommand", "SendAutomationExecution", "StartSession")
| eval caller_arn=userIdentity.arn
| eval caller_type=userIdentity.type
| eval source_ip=sourceIPAddress
| eval document_name=requestParameters.documentName
| eval target_instances=requestParameters.instanceIds
| eval aws_region=awsRegion
| eval error=coalesce(errorCode, "none")
| search error="none"
| stats
count AS command_count,
values(target_instances) AS targeted_instances,
values(document_name) AS documents_used,
dc(target_instances) AS unique_targets
BY caller_arn, caller_type, source_ip, aws_region
| where unique_targets > 1
| sort - command_count Atomic Red Team Tests
Simulates an adversary using Azure RunCommand to execute a PowerShell script on a target VM. Requires Azure CLI authenticated with Contributor or Virtual Machine Contributor rights on the target VM.
Command
az vm run-command invoke --resource-group <ResourceGroupName> --name <VMName> --command-id RunPowerShellScript --scripts "whoami; hostname; Get-LocalUser | Select Name,Enabled; Get-Process | Select-Object Name,Id | Sort-Object Name" Cleanup
az vm run-command list --resource-group <ResourceGroupName> --vm-name <VMName> --query "[].{Name:name, Id:id}" --output table Expected Telemetry
AzureActivity log entry with OperationName 'Microsoft.Compute/virtualMachines/runCommand/action', ActivityStatus 'Succeeded', and Caller set to the authenticated user's UPN or service principal object ID. On the VM: SecurityEvent 4688 showing powershell.exe spawned by WindowsAzureGuestAgent.exe.
Expected Detection
Alert fires on AzureActivity RunCommand invocation detection rule. Secondary alert fires if this is a first-time caller or outside maintenance window.
Simulates adversary use of AWS Systems Manager Run Command to execute arbitrary shell commands on an EC2 instance. Requires AWS CLI authenticated with ssm:SendCommand and ec2:DescribeInstances permissions.
Command
aws ssm send-command --instance-ids <InstanceId> --document-name "AWS-RunShellScript" --parameters commands=["id","hostname","cat /etc/passwd","ps aux | head -20"] --region <Region> --output json Cleanup
aws ssm list-commands --filters Key=DocumentName,Values=AWS-RunShellScript --region <Region> --query 'Commands[*].{CommandId:CommandId,Status:Status,InstanceIds:InstanceIds}' --output table Expected Telemetry
AWS CloudTrail event with eventName 'SendCommand', eventSource 'ssm.amazonaws.com', requestParameters containing documentName 'AWS-RunShellScript' and target instanceId. On the EC2 instance: /var/log/amazon/ssm/amazon-ssm-agent.log entries showing command receipt and execution.
Expected Detection
Alert fires on CloudTrail SendCommand detection rule. If CloudTrail is ingested into Sentinel (AWSCloudTrail table) or Splunk (aws:cloudtrail sourcetype), the hunting query for AWS SSM RunCommand should surface this activity.
Simulates an adversary creating and executing an Azure Automation Runbook to run PowerShell on a Hybrid Worker or to manage Azure resources programmatically. Requires Automation Contributor role on an Automation Account.
Command
az automation runbook create --automation-account-name <AutomationAccountName> --resource-group <ResourceGroupName> --name AtomicTestRunbook --type PowerShell && az automation runbook replace-content --automation-account-name <AutomationAccountName> --resource-group <ResourceGroupName> --name AtomicTestRunbook --content "Write-Output 'Atomic Test Execution'; Get-Date; $env:COMPUTERNAME" && az automation runbook publish --automation-account-name <AutomationAccountName> --resource-group <ResourceGroupName> --name AtomicTestRunbook && az automation runbook start --automation-account-name <AutomationAccountName> --resource-group <ResourceGroupName> --name AtomicTestRunbook Cleanup
az automation runbook delete --automation-account-name <AutomationAccountName> --resource-group <ResourceGroupName> --name AtomicTestRunbook --yes Expected Telemetry
AzureActivity log entries with OperationName 'Microsoft.Automation/automationAccounts/runbooks/write', 'Microsoft.Automation/automationAccounts/jobs/write', and 'Microsoft.Automation/automationAccounts/jobs/read'. Azure Automation job logs in the portal showing execution output.
Expected Detection
Alert fires on AzureActivity Automation job creation detection. If caller is a first-time Automation user, the hunting query for first-time RunCommand callers may also trigger if the identity has no prior RunCommand or Automation history.