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