Azure Policy DeployIfNotExists Action Triggered


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.

Query · python

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

Analyst notes

  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
Raw source Azure Policy DeployIfNotExists Action Triggered · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
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

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.