Azure Automation Runbook Deleted


Description

Detects when an Azure Automation runbook is deleted. Adversaries may delete runbooks to cover their tracks after using them for malicious purposes, to disrupt automated security responses, or to eliminate forensic evidence. Legitimate runbook deletions should be rare and controlled through change management processes.

Query · python

from panther_azureactivity_helpers import (
    azure_activity_alert_context,
    azure_activity_success,
    extract_resource_name_from_id,
)

RUNBOOK_DELETE_OPERATION = "MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/DELETE"


def rule(event):
    return event.get(
        "operationName", ""
    ).upper() == RUNBOOK_DELETE_OPERATION and azure_activity_success(event)


def title(event):
    resource_id = event.get("resourceId", "<UNKNOWN_RESOURCE>")

    runbook_name = extract_resource_name_from_id(
        resource_id, "runbooks", default="<UNKNOWN_RUNBOOK_NAME>"
    )

    return f"Azure Automation Runbook Deleted: [{runbook_name}]"


def alert_context(event):
    context = azure_activity_alert_context(event)

    resource_id = event.get("resourceId", "")

    runbook_name = extract_resource_name_from_id(resource_id, "runbooks", default="")
    if runbook_name:
        context["runbook_name"] = runbook_name

    automation_account_name = extract_resource_name_from_id(
        resource_id, "automationAccounts", default=""
    )
    if automation_account_name:
        context["automation_account_name"] = automation_account_name

    return context

Analyst notes

  1. Query Azure Monitor Activity logs for all runbook operations (create, modify, delete) by the callerIpAddress in the 24 hours before the deletion to identify if the runbook was recently created by the same actor
  2. Find all runbook deletions in the past 6 hours to determine if this is part of a larger cleanup operation
  3. Search for other defense evasion activities (diagnostic settings deletions, alert rule deletions, event hub deletions) from the same caller in the 24 hours around the alert
Raw source Azure Automation Runbook Deleted · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Filename: azure_automation_runbook_deleted.py
RuleID: "Azure.MonitorActivity.Automation.RunbookDeleted"
DisplayName: "Azure Automation Runbook Deleted"
Enabled: true
LogTypes:
  - Azure.MonitorActivity
Severity: Info
Description: >
  Detects when an Azure Automation runbook is deleted. Adversaries may delete runbooks to cover
  their tracks after using them for malicious purposes, to disrupt automated security responses,
  or to eliminate forensic evidence. Legitimate runbook deletions should be rare and controlled
  through change management processes.
Reports:
  MITRE ATT&CK:
    - TA0005:T1070 # Defense Evasion: Indicator Removal
Tags:
  - Defense Evasion
  - Indicator Removal
Runbook: |
  1. Query Azure Monitor Activity logs for all runbook operations (create, modify, delete) by the callerIpAddress in the 24 hours before the deletion to identify if the runbook was recently created by the same actor
  2. Find all runbook deletions in the past 6 hours to determine if this is part of a larger cleanup operation
  3. Search for other defense evasion activities (diagnostic settings deletions, alert rule deletions, event hub deletions) from the same caller in the 24 hours around the alert
Reference: https://github.com/elastic/detection-rules/blob/main/rules/integrations/azure/defense_evasion_azure_automation_runbook_deleted.toml
SummaryAttributes:
  - resourceId
  - callerIpAddress
  - correlationId
Tests:
  - Name: Runbook Deleted Successfully
    ExpectedResult: true
    Log:
      {
        "time": "2025-12-22T10:30:00.0000000Z",
        "resourceId": "/subscriptions/12345678-1234-1234-1234-123456789abc/resourceGroups/automation-rg/providers/Microsoft.Automation/automationAccounts/MyAutomationAccount/runbooks/SuspiciousRunbook",
        "operationName": "MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/DELETE",
        "operationVersion": "2021-06-22",
        "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: Runbook Deleted Case Insensitive
    ExpectedResult: true
    Log:
      {
        "time": "2025-12-22T11:15:00.0000000Z",
        "resourceId": "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/prod-automation/providers/Microsoft.Automation/automationAccounts/ProdAccount/runbooks/OldScript",
        "operationName": "microsoft.automation/automationaccounts/runbooks/delete",
        "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: Different Operation
    ExpectedResult: false
    Log:
      {
        "time": "2025-12-22T13:00:00.0000000Z",
        "resourceId": "/subscriptions/44444444-4444-4444-4444-444444444444/resourceGroups/automation-rg/providers/Microsoft.Automation/automationAccounts/MyAccount/runbooks/MyRunbook",
        "operationName": "MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/WRITE",
        "category": "Administrative",
        "resultType": "Success",
        "callerIpAddress": "4.4.4.4",
        "correlationId": "d4e5f6a7-b8c9-0123-def0-456789012345",
        "tenantId": "44444444-4444-4444-4444-444444444444"
      }

# ------ paired body: azure_automation_runbook_deleted.py ------

from panther_azureactivity_helpers import (
    azure_activity_alert_context,
    azure_activity_success,
    extract_resource_name_from_id,
)

RUNBOOK_DELETE_OPERATION = "MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/DELETE"


def rule(event):
    return event.get(
        "operationName", ""
    ).upper() == RUNBOOK_DELETE_OPERATION and azure_activity_success(event)


def title(event):
    resource_id = event.get("resourceId", "<UNKNOWN_RESOURCE>")

    runbook_name = extract_resource_name_from_id(
        resource_id, "runbooks", default="<UNKNOWN_RUNBOOK_NAME>"
    )

    return f"Azure Automation Runbook Deleted: [{runbook_name}]"


def alert_context(event):
    context = azure_activity_alert_context(event)

    resource_id = event.get("resourceId", "")

    runbook_name = extract_resource_name_from_id(resource_id, "runbooks", default="")
    if runbook_name:
        context["runbook_name"] = runbook_name

    automation_account_name = extract_resource_name_from_id(
        resource_id, "automationAccounts", default=""
    )
    if automation_account_name:
        context["automation_account_name"] = automation_account_name

    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.