Azure VM extension abuse: SYSTEM code with nobody logged in
In Azure you do not need RDP or SSH to run code on a VM. If you can write an extension, the guest agent runs your script as SYSTEM. The fix lives at the control plane, not the host.
In Azure you do not need an interactive logon to run code on a virtual machine. If you can write an extension, the guest agent will run your script as SYSTEM. That makes any role which can push extensions a code-execution role.
How the attack works
An identity with the VM Contributor role pushes a CustomScriptExtension to a production VM, recorded as a Microsoft.Compute/virtualMachines/extensions/write in the Azure Activity Log. The guest agent runs the payload in the SYSTEM context with no RDP or SSH session anywhere. The script pulls a second-stage binary from an external host and then schedules a task that re-applies the extension, so the foothold survives reboots and any cleanup of the running process. The authoritative record is the Activity Log write correlated with the guest extension logs and the VM’s network telemetry. In ATT&CK terms this is T1651, Cloud Administration Command, with T1543, Create or Modify System Process.
Why it works
VM Contributor implicitly grants the ability to write extensions and run code as SYSTEM on any VM in scope, and that privilege was standing rather than gated. extensions/write is remote code execution wearing a control-plane name.
How to fix it
A local administrator password change does nothing, because the scheduled self-reinstall re-applies the extension from the control plane. Remove the malicious extension, isolate the VM at the NIC with a deny-all NSG, and revoke or suspend the role assignment holding extensions/write so it cannot re-push. Afterward, treat any role with extensions/write as code-execution-equivalent, scope it tightly and move it behind just-in-time PIM elevation, and alert on every extension write across the fleet.
Practice it
We built this as a GraphLattice Range scenario so responders contain at the control plane, removing the extension and revoking the role, not chasing a process on the host.