Skip to content

This tool is not affiliated with, endorsed by or sponsored by Microsoft Corporation. Azure, Microsoft Azure and Microsoft Defender for Cloud are trademarks of the Microsoft group of companies. Other names are trademarks of their respective owners.

Azure Run Command attacks: investigating VM execution

How attackers use Azure VM Run Command and the Custom Script Extension, what the Activity Log records (and omits), and which on-VM artifacts hold the script.

Published on 6 min read

TL;DR. Run Command lets anyone with Microsoft.Compute/virtualMachines/runCommand/action execute a script on a VM through the VM agent — as SYSTEM on Windows, as an elevated user on Linux — with no network path to the VM. The Activity Log records who, when, from where and on which VM, but generally not the script. The script lives on the VM: C:\Packages\Plugins\Microsoft.CPlat.Core.RunCommandWindows\<version>\Downloads\ on Windows, /var/lib/waagent/run-command/download/ on Linux. The Custom Script Extension (extensions/write) is the same idea with a download step. Correlate with outbound flows from the VM in the next minutes.

Run Command is the Azure feature I would miss most as an administrator and fear most as a defender. It turns a control-plane permission into code execution on the guest, which is exactly why intruders who obtain Contributor on a subscription use it within minutes.

Why attackers like Run Command

Microsoft describes Run Command as a way to run scripts "within an Azure Windows VM" using the VM agent, useful when RDP or SSH is closed (Windows, Linux). From an attacker's point of view:

Property (per Microsoft Learn)What it means for an attacker
Scripts run as System on Windows, as an elevated user on LinuxImmediate highest privilege on the guest
Needs virtualMachines/runCommand/action; Virtual Machine Contributor and higher have itAny stolen Contributor-level identity is enough
Works without RDP/SSH; the agent pulls the jobNSGs and firewalls do not stop it
Output limited to the last 4,096 bytes, max 90 minutesAttackers run a downloader and leave

The typical sequence: run RunShellScript or RunPowerShellScript with a one-liner that downloads a second stage, dumps credentials, or queries the Instance Metadata Service for the VM's managed identity token. That last one turns VM execution into a new cloud identity (managed identity abuse).

MITRE ATT&CK tracks this as T1651 Cloud Administration Command. Microsoft's own Azure Threat Research Matrix lists it as AZT301.1.

What the Activity Log shows

OperationMeaning
Microsoft.Compute/virtualMachines/runCommand/action"Action" Run Command on a VM (the classic one)
Microsoft.Compute/virtualMachines/runCommands/writeManaged Run Command resource created or updated
Microsoft.Compute/virtualMachineScaleSets/virtualMachines/runCommand/actionRun Command on a scale-set instance
Microsoft.HybridCompute/machines/runCommands/writeRun Command on an Azure Arc-enabled server
Microsoft.Compute/virtualMachines/extensions/writeExtension installed or updated (Custom Script Extension, among others)
Microsoft.SerialConsole/serialPorts/connect/actionSerial console connection

Each Run Command typically produces a Started, an Accepted and a Succeeded event sharing one correlationId. You get the caller, the claims, the caller IP and the target VM. What you usually do not get is the script. Mandiant's Azure Run Command for Dummies is explicit that the Activity Log shows the action but not the script contents; recovering them requires the VM.

Where the script lives on the VM

From Mandiant's research and Microsoft's troubleshooting notes:

OSArtifactPath
WindowsScript bodyC:\Packages\Plugins\Microsoft.CPlat.Core.RunCommandWindows\<version>\Downloads\script<n>.ps1
WindowsStatus and outputC:\Packages\Plugins\Microsoft.CPlat.Core.RunCommandWindows\<version>\Status\<n>.status
WindowsExtension logC:\WindowsAzure\Logs\Plugins\Microsoft.CPlat.Core.RunCommandWindows\<version>\RunCommandExtension.log
LinuxScript, stdout, stderr/var/lib/waagent/run-command/download/<n>/ (script.sh, stdout, stderr)
LinuxHandler log (timestamps, no script)/var/log/azure/run-command/handler.log

On Windows, RunPowerShellScript also leaves PowerShell telemetry (script block logging, event ID 4104) if it was enabled. Collect these files before rebuilding the VM: snapshot the OS disk, then image or mount it read-only.

The Custom Script Extension variant

The Custom Script Extension downloads files from fileUris and runs commandToExecute. It is installed with Microsoft.Compute/virtualMachines/extensions/write; the publisher and type are Microsoft.Compute / CustomScriptExtension on Windows and Microsoft.Azure.Extensions / CustomScript on Linux (Windows, Linux).

Forensic notes:

  • The command can sit in settings (clear text in the request body) or protectedSettings (encrypted to the VM). Attackers who know this use protected settings.
  • On Windows it runs as LocalSystem; downloads go to C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.*\Downloads\<n> and logs to C:\WindowsAzure\Logs\Plugins\Microsoft.Compute.CustomScriptExtension.
  • On Linux, downloads and stdout / stderr go to /var/lib/waagent/custom-script/download/<n>/, and the handler log is /var/log/azure/custom-script/handler.log.
  • az vm extension list shows the current public settings, including commandToExecute if it was not protected. It shows only the current state, not history.

What the analyzer flags

RuleDetectsLevel
AZ-VM-001Run Command on a VM, scale-set instance or Arc machineHigh
AZ-VM-002Custom Script Extension deployed (extensions/write with a custom-script type)High
AZ-VM-003Serial console connectionMedium
AZ-VM-004VM created in a region not used during the learning periodMedium
AZ-VM-005Disk or snapshot exported with a SAS URL (beginGetAccess)High
AZ-VM-006Disk snapshot createdLow
AZ-NET-004Flow logs show traffic with an IP address involved in other findingsHigh

AZ-VM-001 is noisy in environments that use Run Command for administration. That is fine: the point is to put each execution in front of a human, with its caller and IP, and the Azure Forensics analyzer timeline shows what happened right after.

Triage checklist

  1. Who ran it? A human administrator from the corporate IP range during working hours, or a service principal that normally deploys web apps?
  2. Is the caller's IP new? AZ-ID-001 flags service principals used from IPs never seen before.
  3. What did the VM do next? In VNet or NSG flow logs, look for outbound connections from the VM's private IP to a public IP in the next few minutes. In the fictional sample, the VM connects out to the attacker IP on port 80 roughly 25 seconds after the Run Command starts.
  4. Did the VM's managed identity appear elsewhere? A managed identity token used from an IP other than the VM's is the signature of IMDS token theft (AZ-ID-002).
  5. Get the script from the disk snapshot, at the paths above.
  6. Check for disk export. beginGetAccess on a disk or snapshot returns a download URL for the whole disk (MITRE T1537).

Remediation

  • Isolate the VM (deny-all NSG), snapshot its disks for forensics, rebuild it from a known-good image.
  • Remove unknown extensions and managed run commands; remove the RunCommand extension if you do not use it.
  • Rotate credentials that were on the VM or reachable by its managed identity.
  • Limit runCommand/action with custom roles; Virtual Machine Contributor grants it.

Related articles

How attackers abuse Azure service principals and managed identities: leaked secrets, IMDS token theft, federated credentials, runbooks, and the log signals.
How attackers escalate with Azure role assignments and elevateAccess, what roleAssignments/write events look like in the Activity Log, and how to triage them.
Think your Azure subscription is compromised? Which logs to preserve first, the operations that betray an attacker, and how to go from Activity Log to verdict.

This tool is not affiliated with, endorsed by or sponsored by Microsoft Corporation. Azure, Microsoft Azure and Microsoft Defender for Cloud are trademarks of the Microsoft group of companies. Other names are trademarks of their respective owners.