AnalysisType: rule
Filename: azure_serverless_execution.py
RuleID: "Azure.MonitorActivity.Automation.ServerlessExecution"
DisplayName: "Azure Serverless Script Execution"
Enabled: true
LogTypes:
- Azure.MonitorActivity
Severity: Info
Description: >
Detects when serverless resources execute PowerShell or Python scripts through Azure Automation
runbook jobs or Azure Function Apps. Adversaries may abuse access to serverless resources to
execute commands with inherited permissions from managed identities, RunAs accounts, or hybrid
worker groups.
Reports:
MITRE ATT&CK:
- TA0002:T1059 # Execution: Command and Scripting Interpreter
- TA0002:T1651 # Execution: Cloud Administration Command
Tags:
- AZT404
- AZT404.3
- AZT302
- AZT302.1
- AZT302.2
- AZT302.3
- AZT302.4
- Execution
- Command and Scripting Interpreter
- Cloud Administration Command
Runbook: |
1. Query Azure Monitor Activity logs for all automation and function app execution operations by the callerIpAddress in the 24 hours before and after the alert to identify patterns of serverless execution
2. Find all runbook or function app creation/modification activities by the same callerIpAddress in the 6 hours before the alert to determine if this is part of a setup-then-execute attack pattern
3. Check if the callerIpAddress has executed runbook jobs or function apps in the past 90 days to establish if this is typical administrative activity or anomalous behavior
Reference: https://microsoft.github.io/Azure-Threat-Research-Matrix/Execution/AZT302/AZT302/
SummaryAttributes:
- resourceId
- callerIpAddress
- correlationId
Tests:
- Name: Automation Runbook Job Executed
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/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"operationName": "MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/JOBS/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: Runbook Job 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/jobs/b2c3d4e5-f6a7-8901-bcde-f23456789012",
"operationName": "microsoft.automation/automationaccounts/jobs/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: Function App Execution
ExpectedResult: true
Log:
{
"time": "2025-12-22T12:00:00.0000000Z",
"resourceId": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/function-rg/providers/Microsoft.Web/sites/myFunctionApp",
"operationName": "MICROSOFT.WEB/SITES/HOSTRUNTIME/HOST/ACTION",
"category": "Administrative",
"resultType": "Success",
"callerIpAddress": "3.3.3.3",
"correlationId": "c3d4e5f6-a7b8-9012-cdef-222222222222",
"level": "Informational",
"location": "centralus",
"tenantId": "22222222-2222-2222-2222-222222222222"
}
- Name: Function App Case Insensitive
ExpectedResult: true
Log:
{
"time": "2025-12-22T13:00:00.0000000Z",
"resourceId": "/subscriptions/33333333-3333-3333-3333-333333333333/resourceGroups/apps-rg/providers/Microsoft.Web/sites/prodFunctionApp",
"operationName": "microsoft.web/sites/hostruntime/host/action",
"category": "Administrative",
"resultType": "Succeeded",
"callerIpAddress": "4.4.4.4",
"correlationId": "d4e5f6a7-b8c9-0123-def0-333333333333",
"level": "Information",
"location": "westus",
"tenantId": "33333333-3333-3333-3333-333333333333"
}
- Name: Failed Runbook Job Execution
ExpectedResult: false
Log:
{
"time": "2025-12-22T14:00:00.0000000Z",
"resourceId": "/subscriptions/44444444-4444-4444-4444-444444444444/resourceGroups/automation-rg/providers/Microsoft.Automation/automationAccounts/TestAccount/jobs/e5f6a7b8-c9d0-1234-ef01-444444444444",
"operationName": "MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/JOBS/WRITE",
"category": "Administrative",
"resultType": "Failed",
"resultSignature": "403",
"callerIpAddress": "5.5.5.5",
"correlationId": "e5f6a7b8-c9d0-1234-ef01-444444444444",
"level": "Error",
"location": "eastus2",
"tenantId": "44444444-4444-4444-4444-444444444444"
}
- Name: Failed Function App Execution
ExpectedResult: false
Log:
{
"time": "2025-12-22T15:00:00.0000000Z",
"resourceId": "/subscriptions/55555555-5555-5555-5555-555555555555/resourceGroups/apps-rg/providers/Microsoft.Web/sites/testFunctionApp",
"operationName": "MICROSOFT.WEB/SITES/HOSTRUNTIME/HOST/ACTION",
"category": "Administrative",
"resultType": "Failed",
"resultSignature": "403",
"callerIpAddress": "6.6.6.6",
"correlationId": "f6a7b8c9-d0e1-2345-f012-555555555555",
"level": "Error",
"location": "northeurope",
"tenantId": "55555555-5555-5555-5555-555555555555"
}
- Name: Different Operation
ExpectedResult: false
Log:
{
"time": "2025-12-22T17:00:00.0000000Z",
"resourceId": "/subscriptions/77777777-7777-7777-7777-777777777777/resourceGroups/apps-rg/providers/Microsoft.Web/sites/myApp",
"operationName": "MICROSOFT.WEB/SITES/READ",
"category": "Administrative",
"resultType": "Success",
"callerIpAddress": "8.8.8.8",
"correlationId": "b8c9d0e1-f2a3-4567-1234-777777777777",
"tenantId": "77777777-7777-7777-7777-777777777777"
}
# ------ paired body: azure_serverless_execution.py ------
from panther_azureactivity_helpers import (
azure_activity_alert_context,
azure_activity_success,
extract_resource_name_from_id,
)
# AZT302.1/2/3 - Automation Account Runbook Job Execution
RUNBOOK_JOB_WRITE = "MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/JOBS/WRITE"
# AZT302.4 - Function Application Execution
FUNCTION_APP_ACTION = "MICROSOFT.WEB/SITES/HOSTRUNTIME/HOST/ACTION"
AZT302_OPERATIONS = [
RUNBOOK_JOB_WRITE,
FUNCTION_APP_ACTION,
]
def rule(event):
operation = event.get("operationName", "").upper()
return all([operation in AZT302_OPERATIONS, azure_activity_success(event)])
def title(event):
resource_id = event.get("resourceId", "<UNKNOWN_RESOURCE>")
operation = event.get("operationName", "").upper()
if operation == RUNBOOK_JOB_WRITE:
technique = "Automation Runbook Job"
resource_name = extract_resource_name_from_id(
resource_id, "automationAccounts", default="<UNKNOWN_ACCOUNT>"
)
elif operation == FUNCTION_APP_ACTION:
technique = "Function App"
resource_name = extract_resource_name_from_id(resource_id, "sites", default="<UNKNOWN_APP>")
else:
technique = "Serverless"
resource_name = "<UNKNOWN_RESOURCE>"
return f"Azure {technique} Execution detected on [{resource_name}]"
def alert_context(event):
context = azure_activity_alert_context(event)
operation = event.get("operationName", "").upper()
resource_id = event.get("resourceId", "")
if operation == RUNBOOK_JOB_WRITE:
context["resource_type"] = "Automation Runbook Job"
automation_account = extract_resource_name_from_id(
resource_id, "automationAccounts", default=""
)
if automation_account:
context["automation_account"] = automation_account
runbook_name = extract_resource_name_from_id(resource_id, "runbooks", default="")
if runbook_name:
context["runbook_name"] = runbook_name
job_id = extract_resource_name_from_id(resource_id, "jobs", default="")
if job_id:
context["job_id"] = job_id
elif operation == FUNCTION_APP_ACTION:
context["resource_type"] = "Function App"
function_app_name = extract_resource_name_from_id(resource_id, "sites", default="")
if function_app_name:
context["function_app_name"] = function_app_name
return context