AnalysisType: rule
Filename: azure_policy_deployifnotexists.py
RuleID: "Azure.MonitorActivity.Policy.DeployIfNotExists"
DisplayName: "Azure Policy DeployIfNotExists Action Triggered"
Enabled: true
LogTypes:
- Azure.MonitorActivity
Severity: Medium
Description: >
Detects when an Azure Policy with the DeployIfNotExists effect is triggered and executes a deployment.
The DeployIfNotExists effect allows policies to automatically deploy resources when certain conditions are met.
Adversaries may abuse this feature to establish persistence by creating policies that automatically deploy
backdoors, malicious configurations, or unauthorized resources when specific conditions occur. This technique
enables stealthy persistence as deployments appear to be legitimate policy enforcement actions.
Reports:
MITRE ATT&CK:
- TA0003:T1078.004 # Persistence: Valid Accounts - Cloud Accounts
- TA0005:T1564 # Defense Evasion: Hide Artifacts
Tags:
- AZT508
- Persistence
- Defense Evasion
- Valid Accounts
- Cloud Accounts
- Hide Artifacts
Runbook: |
1. Find all policy-related operations by the callerIpAddress in the 48 hours before and after this alert to identify if new policies were created or assignments were modified
2. Query for the policy definition details to understand what resources are being deployed and review the deployment template for malicious configurations
3. Check if similar DeployIfNotExists actions have been triggered from this policy in the past 30 days to determine if this is expected behavior
Reference: https://microsoft.github.io/Azure-Threat-Research-Matrix/Persistence/AZT508/AZT508
SummaryAttributes:
- resourceId
- callerIpAddress
- correlationId
Tests:
- Name: DeployIfNotExists Policy Triggered
ExpectedResult: true
Log:
{
"time": "2025-12-23T10:30:00.0000000Z",
"resourceId": "/subscriptions/12345678-1234-1234-1234-123456789abc/resourceGroups/policy-rg/providers/Microsoft.Authorization/policyDefinitions/AutoDeployBackdoor",
"operationName": "Microsoft.Authorization/policies/deployIfNotExists/action",
"operationVersion": "2021-06-01",
"category": "Administrative",
"resultType": "Success",
"resultSignature": "200",
"callerIpAddress": "1.1.1.1",
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"level": "Informational",
"location": "eastus",
"tenantId": "87654321-4321-4321-4321-111111111111"
}
- Name: Policy Deployment Success
ExpectedResult: true
Log:
{
"time": "2025-12-23T11:15:00.0000000Z",
"resourceId": "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/prod-rg/providers/Microsoft.Authorization/policyAssignments/AutoDeploy",
"operationName": "Microsoft.Authorization/policies/deployIfNotExists/action",
"category": "Administrative",
"resultType": "Succeeded",
"callerIpAddress": "2.2.2.2",
"correlationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"level": "Information",
"location": "westeurope",
"tenantId": "22222222-2222-2222-2222-222222222222"
}
- Name: Case Insensitive Match
ExpectedResult: true
Log:
{
"time": "2025-12-23T12:00:00.0000000Z",
"resourceId": "/subscriptions/33333333-3333-3333-3333-333333333333/resourceGroups/security-rg/providers/Microsoft.Authorization/policyDefinitions/MaliciousPolicy",
"operationName": "microsoft.authorization/policies/deployifnotexists/action",
"category": "Administrative",
"resultType": "Success",
"callerIpAddress": "3.3.3.3",
"correlationId": "c3d4e5f6-a7b8-9012-cdef-333333333333",
"level": "Informational",
"location": "centralus",
"tenantId": "33333333-3333-3333-3333-333333333333"
}
- Name: Different Operation
ExpectedResult: false
Log:
{
"time": "2025-12-23T14:00:00.0000000Z",
"resourceId": "/subscriptions/55555555-5555-5555-5555-555555555555/resourceGroups/policy-rg/providers/Microsoft.Authorization/policyDefinitions/MyPolicy",
"operationName": "Microsoft.Authorization/policyDefinitions/write",
"category": "Administrative",
"resultType": "Success",
"callerIpAddress": "5.5.5.5",
"correlationId": "e5f6a7b8-c9d0-1234-ef01-111111111111",
"tenantId": "55555555-5555-5555-5555-555555555555"
}
# ------ paired body: azure_policy_deployifnotexists.py ------
from panther_azureactivity_helpers import (
azure_activity_alert_context,
azure_activity_success,
extract_resource_name_from_id,
)
POLICY_OPERATIONS = [
"MICROSOFT.AUTHORIZATION/POLICIES/DEPLOYIFNOTEXISTS/ACTION",
]
def rule(event):
return event.get("operationName", "").upper() in POLICY_OPERATIONS and azure_activity_success(
event
)
def title(event):
resource_id = event.get("resourceId", "<UNKNOWN_RESOURCE>")
policy_name = extract_resource_name_from_id(
resource_id, "policyDefinitions", default="<UNKNOWN_POLICY>"
)
return f"Azure Policy DeployIfNotExists Triggered: [{policy_name}]"
def alert_context(event):
context = azure_activity_alert_context(event)
resource_id = event.get("resourceId", "")
policy_name = extract_resource_name_from_id(resource_id, "policyDefinitions", default="")
if policy_name:
context["policy_name"] = policy_name
policy_assignment = extract_resource_name_from_id(resource_id, "policyAssignments", default="")
if policy_assignment:
context["policy_assignment"] = policy_assignment
return context