AnalysisType: rule
Filename: azure_automation_schedule_created.py
RuleID: "Azure.MonitorActivity.Automation.ScheduleCreated"
DisplayName: "Azure Automation Schedule Created or Modified"
Enabled: true
LogTypes:
- Azure.MonitorActivity
Severity: Info
Description: >
Detects when an Azure Automation schedule is created or modified. Schedules define when and how often
automation runbooks execute. Adversaries may create or modify schedules to establish persistence by
executing malicious runbooks at regular intervals or specific times. This technique allows attackers
to maintain access and execute commands without requiring direct interaction.
Reports:
MITRE ATT&CK:
- TA0003:T1053.005 # Persistence: Scheduled Task/Job - Scheduled Task
- TA0004:T1068 # Privilege Escalation: Exploitation for Privilege Escalation
Tags:
- AZT505
- Persistence
- Privilege Escalation
- Scheduled Task
- Exploitation for Privilege Escalation
Runbook: |
1. Find all automation account operations by the callerIpAddress in the 24 hours before and after this alert to identify if runbooks were created or modified alongside this schedule
2. Query for runbook job execution events from the automation account in the 6 hours after the schedule creation to determine if the schedule has already triggered
3. Check if the callerIpAddress is associated with known cloud providers, VPNs, or corporate network ranges used by authorized DevOps personnel
Reference: https://microsoft.github.io/Azure-Threat-Research-Matrix/Persistence/AZT505/AZT505-1
SummaryAttributes:
- resourceId
- callerIpAddress
- correlationId
Tests:
- Name: Schedule Created Successfully
ExpectedResult: true
Log:
{
"time": "2025-12-23T10:30:00.0000000Z",
"resourceId": "/subscriptions/12345678-1234-1234-1234-123456789abc/resourceGroups/automation-rg/providers/Microsoft.Automation/automationAccounts/MyAutomationAccount/schedules/DailyMaintenanceSchedule",
"operationName": "MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/SCHEDULES/WRITE",
"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: Schedule Modified
ExpectedResult: true
Log:
{
"time": "2025-12-23T11:15:00.0000000Z",
"resourceId": "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/prod-automation/providers/Microsoft.Automation/automationAccounts/ProdAccount/schedules/HourlyBackup",
"operationName": "MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/SCHEDULES/WRITE",
"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: Persistence Schedule Created
ExpectedResult: true
Log:
{
"time": "2025-12-23T12:00:00.0000000Z",
"resourceId": "/subscriptions/33333333-3333-3333-3333-333333333333/resourceGroups/compromised-rg/providers/Microsoft.Automation/automationAccounts/CompromisedAccount/schedules/MaliciousSchedule",
"operationName": "microsoft.automation/automationaccounts/schedules/write",
"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-23T15:00:00.0000000Z",
"resourceId": "/subscriptions/66666666-6666-6666-6666-666666666666/resourceGroups/automation-rg/providers/Microsoft.Automation/automationAccounts/MyAccount/schedules/OldSchedule",
"operationName": "MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/SCHEDULES/DELETE",
"category": "Administrative",
"resultType": "Success",
"callerIpAddress": "6.6.6.6",
"correlationId": "f6a7b8c9-d0e1-2345-f012-678901234567",
"tenantId": "66666666-6666-6666-6666-666666666666"
}
# ------ paired body: azure_automation_schedule_created.py ------
from panther_azureactivity_helpers import (
azure_activity_alert_context,
azure_activity_success,
extract_resource_name_from_id,
)
SCHEDULE_OPERATIONS = [
"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/SCHEDULES/WRITE",
]
def rule(event):
return event.get("operationName", "").upper() in SCHEDULE_OPERATIONS and azure_activity_success(
event
)
def title(event):
resource_id = event.get("resourceId", "<UNKNOWN_RESOURCE>")
caller = event.get("callerIpAddress", "<UNKNOWN_CALLER>")
schedule_name = extract_resource_name_from_id(
resource_id, "schedules", default="<UNKNOWN_SCHEDULE_NAME>"
)
return f"Azure Automation Schedule Created or Modified: [{schedule_name}] by [{caller}]"
def alert_context(event):
context = azure_activity_alert_context(event)
resource_id = event.get("resourceId", "")
schedule_name = extract_resource_name_from_id(resource_id, "schedules", default="")
if schedule_name:
context["schedule_name"] = schedule_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