Living-Off-the-Cloud: Logic Apps as Covert C2 and Persistence

The attacker did not deploy malware. They did not use a custom C2 framework. They used Azure Logic Apps — the same service your HR team uses for onboarding automation. The traffic was indistinguishable from a legitimate business workflow. The managed identity had Key Vault access. The persistence survived the incident response team’s VM wipe. And it ran for 11 days before anyone looked at Logic Apps.

This article is about what that attack looks like, why it is so hard to detect, and how to build the detection framework that most organizations have never configured. The technique is real, documented in Microsoft’s own threat intelligence, and your Azure subscription almost certainly has zero coverage for it right now.


Understanding the Foundation

What Is “Living Off the Cloud”?

For decades, attackers on Windows systems have used a technique called Living Off the Land — using Windows’ own built-in tools like PowerShell, WMI, certutil, and scheduled tasks to conduct operations instead of deploying custom malware. The logic is simple: if your attack traffic looks identical to legitimate administrative activity, signature-based detection is blind to it.

Living Off the Cloud applies the same thinking to cloud environments. Instead of deploying a custom C2 server on a rented VPS with a sketchy IP reputation, the attacker uses the cloud platform itself — Azure Logic Apps, AWS Lambda, Google Cloud Functions, Azure Automation Runbooks — as their command infrastructure. These services are operated by Microsoft, Amazon, and Google. Their IP ranges are on every allowlist. Their traffic is encrypted by default. And most organizations have built zero detection coverage for abuse of their own automation infrastructure.

The shift matters because the traditional indicators of compromise that defenders rely on — unusual outbound connections, unknown process execution, suspicious binary hashes, flagged IP addresses — simply do not apply when the “malware” is a native cloud service making API calls over HTTPS to other native cloud services.

Why Azure Logic Apps Specifically?

Azure Logic Apps is Microsoft’s workflow automation platform. It allows organizations to build automated processes that connect different services send an email when a new record appears in SharePoint, trigger a Teams notification when a support ticket is created, sync data between Salesforce and Azure SQL, run approval workflows for procurement. Thousands of organizations use it for legitimate business operations every day.

From an attacker’s perspective, Logic Apps have a set of properties that make them unusually attractive as persistent infrastructure:

They are serverless. Logic Apps do not run on virtual machines. Reimaging a compromised VM does not touch them. Rotating compromised service account credentials does not affect them. Disabling a compromised user account does not stop them. The attacker’s Logic App keeps running through standard incident response procedures that would neutralize most other persistence mechanisms.

They support managed identities. A Logic App can be assigned a system-assigned or user-assigned managed identity — a cloud identity that Azure manages automatically with no stored credentials. This managed identity can be granted RBAC roles on any Azure resource: Storage Accounts, Key Vaults, SQL databases, other Logic Apps, Azure Functions, entire subscriptions. When the Logic App runs, it authenticates as this identity silently in the background with no user sign-in event, no Entra ID interactive sign-in log entry, and no credential that can be rotated or revoked.

They blend completely with legitimate traffic. A Logic App making API calls to Azure Storage or Azure Key Vault looks identical in logs to a legitimate business automation workflow doing the same thing. The source IP is a Microsoft-owned Azure data center IP. The user agent is a standard Azure service user agent. The authentication is a valid managed identity token. There is no behavioral anomaly that signature-based detection can flag.

They run on a schedule. Logic Apps can be triggered by HTTP requests, by schedules (every 5 minutes, every hour, at specific times), by events in Azure Storage or Service Bus, or by a wide variety of other triggers. A scheduled polling loop that checks a storage blob for commands, executes them, and writes results back is functionally a C2 beaconing pattern implemented entirely in the Azure portal with no custom code.

They cost almost nothing. The Logic Apps consumption plan charges approximately $0.000025 per action execution. A C2 loop running every 5 minutes costs a few cents per month negligible against any organization’s Azure bill, and therefore invisible in cost anomaly monitoring.

The Managed Identity Attack Surface The Core of the Problem

To understand why Logic App abuse is so impactful, you need to understand managed identities and how they differ from traditional service account credentials.

Traditional service account: A human creates a service account, assigns it a password or client secret, grants it permissions to resources, and embeds the credentials somewhere (a config file, an Azure Key Vault secret, an environment variable). The credential can be discovered, rotated, or revoked. Entra ID logs show every authentication event for that service principal. Security teams can audit the permissions.

Managed identity: Azure automatically creates a service principal for a resource (like a Logic App) and manages its cryptographic credentials entirely. There is no stored password, no client secret, no certificate that a human ever handles. The resource authenticates to Azure services by requesting a short-lived token from the Azure Instance Metadata Service endpoint — a local API call that never leaves the Azure infrastructure. This token is used directly to call Azure APIs. There is no Entra ID interactive sign-in log entry. There is no credential for a security team to rotate. The identity’s permissions are controlled entirely through Azure RBAC role assignments, and if those role assignments are not audited, the access is effectively invisible.

When an attacker compromises a resource with a managed identity — or creates a new Logic App and assigns it a managed identity with elevated permissions — they gain persistent access to whatever resources that identity has been granted access to, with authentication that generates no alerts and leaves minimal forensic evidence.

MITRE ATT&CK Coverage Where This Technique Lives

This technique maps to two MITRE ATT&CK cloud technique entries that are frequently undercovered in organizational detection frameworks:

T1648 Serverless Execution: The use of serverless cloud functions and workflow services as execution environments. The MITRE framework notes that adversaries may use services like Azure Logic Apps, AWS Lambda, and Google Cloud Functions to execute code or automate tasks in victim environments, with the key advantage that these executions leave minimal traditional forensic evidence and blend with legitimate cloud service usage.

T1098.003 Account Manipulation: Additional Cloud Credentials: The assignment of managed identities with elevated permissions creates additional access paths that can persist independently of the original compromise vector. An attacker who assigns a managed identity to a Logic App and grants it Key Vault Secrets User on a production Key Vault has created a persistent credential that survives the rotation of every other credential in the environment.

T1078.004 Valid Accounts: Cloud Accounts: Managed identity tokens are valid Azure authentication credentials. Using them to access resources is indistinguishable from legitimate service-to-service authentication. This technique appears in real-world APT playbooks specifically because of how difficult it is to distinguish from normal cloud operations.

Real-World Precedent This Is Not Theoretical

Microsoft’s Digital Defense Report 2025 documents nation-state actors using legitimate cloud services as command and control infrastructure. The shift from custom malware toward living-off-the-cloud techniques accelerated significantly in 2024 and 2025, with actors including groups attributed to China, Russia, and Iran all documented using cloud-native services for persistence and lateral movement.

The specific pattern of using serverless execution environments combined with managed identities appears in incident response reports from multiple cloud security firms. The consistent finding is that the technique was not detected by traditional endpoint security tools, not detected by Entra ID Identity Protection (because managed identity authentications are not evaluated for risk), and not detected by Microsoft Defender for Cloud in default configurations (because the resource access was technically authorized).

Lina Lau’s research on living-off-the-cloud techniques presented at DEF CON 2025 documented specific cases of Logic App abuse in production Azure environments, including the use of HTTP triggers as C2 channels and scheduled workflows as beaconing mechanisms. The research noted that most affected organizations had no Logic App monitoring configured at all not because the logging was unavailable, but because nobody had thought to enable it.

Why Traditional Defenses Fail

Understanding exactly why this technique evades standard defenses is important for building detection that actually works.

Endpoint Detection and Response (EDR): EDR tools monitor processes, file system activity, network connections, and memory on endpoint machines. Logic Apps run entirely in Microsoft’s Azure infrastructure there is no endpoint. There is no process to monitor, no file to scan, no network connection from a corporate machine.

Email and Identity Protection: Entra ID Identity Protection evaluates sign-in risk for interactive user authentications. Managed identity token acquisitions are not interactive sign-ins they use the Azure Instance Metadata Service internally and are not evaluated by Identity Protection. The tool simply does not see them.

Microsoft Defender for Cloud: Defender for Cloud’s default configuration alerts on known attack patterns against Azure resources brute force attempts, credential stuffing, exploitation of known vulnerabilities, unusual geographic sign-ins. A Logic App with a properly assigned managed identity accessing Storage and Key Vault within its granted RBAC permissions generates no Defender for Cloud alert because everything it does is technically authorized.

Firewall and Network Monitoring: Logic App executions make API calls from Microsoft’s Azure service IP ranges over HTTPS port 443. These are allowlisted in every enterprise firewall and proxy configuration. Network monitoring tools cannot distinguish an attacker’s Logic App from a legitimate business workflow.

SIEM Default Rules: Most organizations’ SIEM implementations focus on identity events (sign-ins, role changes, MFA registrations), endpoint events (process creation, network connections, file modifications), and network events (firewall alerts, proxy blocks, IDS signatures). Logic App execution events land in Azure Diagnostics logs, which are not a standard SIEM data source in most configurations. Without explicit Logic App diagnostic logging enabled and forwarded to Sentinel, the executions are invisible to the SIEM entirely.


Lab Setup


The Detection Framework

Building detection coverage for Logic App abuse requires four layers: inventory visibility, execution monitoring, behavioral analytics, and managed identity access auditing. The following covers each layer with complete implementation guidance.

Layer 1 Logic App Inventory with Managed Identity Audit

The first step is understanding what Logic Apps exist in your environment and which ones have managed identities with sensitive RBAC permissions. Most organizations cannot answer either question.

Azure Resource Graph Query All Logic Apps with Managed Identities

Run this in the Azure Portal Resource Graph Explorer or in Sentinel via the arg() function:

Resources
| where type == "microsoft.logic/workflows"
| extend identityType = tostring(identity.type)
| extend systemAssigned = identity.principalId
| extend userAssigned = tostring(bag_keys(identity.userAssignedIdentities))
| where identityType != "" and identityType != "None"
| extend triggerType = tostring(properties.definition.triggers)
| project
    name,
    resourceGroup,
    location,
    subscriptionId,
    identityType,
    systemAssigned,
    userAssigned,
    state = tostring(properties.state),
    createdTime = tostring(properties.createdTime),
    changedTime = tostring(properties.changedTime)
| order by changedTime desc

This query returns every Logic App in your subscription that has a managed identity assigned. Any Logic App here that you cannot explain with a known business purpose is a finding.

PowerShell Enumerate All Logic App Managed Identity Role Assignments

# Connect to Azure
Connect-AzAccount

# Get all Logic Apps across all subscriptions
$subscriptions = Get-AzSubscription
$logicAppInventory = @()

foreach ($sub in $subscriptions) {
    Set-AzContext -SubscriptionId $sub.Id | Out-Null
    
    $logicApps = Get-AzLogicApp
    
    foreach ($app in $logicApps) {
        # Check if managed identity is assigned
        if ($app.Identity) {
            $principalId = $app.Identity.PrincipalId
            
            if ($principalId) {
                # Get all role assignments for this managed identity
                $roleAssignments = Get-AzRoleAssignment -ObjectId $principalId -ErrorAction SilentlyContinue
                
                foreach ($role in $roleAssignments) {
                    $logicAppInventory += [PSCustomObject]@{
                        SubscriptionId  = $sub.Id
                        SubscriptionName = $sub.Name
                        LogicAppName    = $app.Name
                        ResourceGroup   = $app.ResourceGroupName
                        Location        = $app.Location
                        State           = $app.State
                        IdentityType    = $app.Identity.Type
                        PrincipalId     = $principalId
                        RoleName        = $role.RoleDefinitionName
                        Scope           = $role.Scope
                        ResourceType    = ($role.Scope -split '/')[-2]
                    }
                }
            }
        }
    }
}

# Output findings
$logicAppInventory | Format-Table -AutoSize

# Flag high-risk role assignments
$highRiskRoles = @(
    "Owner", "Contributor", "User Access Administrator",
    "Key Vault Secrets Officer", "Key Vault Administrator",
    "Storage Blob Data Contributor", "Storage Account Contributor",
    "Virtual Machine Contributor", "Security Admin"
)

Write-Host "`n=== HIGH RISK LOGIC APP ROLE ASSIGNMENTS ==="
$logicAppInventory | Where-Object { $_.RoleName -in $highRiskRoles } |
    Select-Object LogicAppName, ResourceGroup, RoleName, Scope |
    Format-Table -AutoSize

# Export to CSV for review
$logicAppInventory | Export-Csv "logic_app_inventory_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
Write-Host "Full inventory saved to CSV"

Any Logic App with Owner, Contributor, Key Vault Administrator, or User Access Administrator role assignments warrants immediate investigation. These permission levels would allow the Logic App to read secrets, create additional identities, modify other resources, or even assign itself additional permissions.

Layer 2 Enable Logic App Diagnostic Logging

Logic App execution logs are not sent to Sentinel by default. You must explicitly configure diagnostic settings to forward them. Without this step, all subsequent detection rules have no data to query.

Enable Diagnostic Settings via Azure Portal

For each Logic App you want to monitor:

  1. Open the Logic App in the Azure Portal
  2. In the left menu under Monitoring click Diagnostic settings
  3. Click Add diagnostic setting
  4. Give it a name: LogicApp-Security-Diagnostics
  5. Under Logs select:
    • WorkflowRuntime execution events, trigger events, action results
    • AllMetrics performance and execution metrics
  6. Under Destination details select Send to Log Analytics workspace
  7. Choose your Sentinel Log Analytics workspace
  8. Click Save

Enable at Scale via PowerShell

$workspaceId = "/subscriptions/SUBSCRIPTION_ID/resourcegroups/RG_NAME/providers/microsoft.operationalinsights/workspaces/WORKSPACE_NAME"

$logicApps = Get-AzLogicApp

foreach ($app in $logicApps) {
    $resourceId = $app.Id
    
    # Check if diagnostics already enabled
    $existing = Get-AzDiagnosticSetting -ResourceId $resourceId -ErrorAction SilentlyContinue
    
    if (-not $existing) {
        Write-Host "Enabling diagnostics for: $($app.Name)"
        
        $log = New-AzDiagnosticSettingLogSettingsObject `
            -Category "WorkflowRuntime" `
            -Enabled $true `
            -RetentionPolicyDay 90 `
            -RetentionPolicyEnabled $true
        
        New-AzDiagnosticSetting `
            -ResourceId $resourceId `
            -Name "LogicApp-Security-Diagnostics" `
            -WorkspaceId $workspaceId `
            -Log $log
            
        Write-Host "  Done: $($app.Name)"
    } else {
        Write-Host "  Already configured: $($app.Name)"
    }
}

After enabling diagnostic settings, Logic App execution data flows into the AzureDiagnostics table in your Log Analytics workspace, filterable by ResourceType == "MICROSOFT.LOGIC/WORKFLOWS".

Layer 3 Sentinel KQL Detection Rules

With diagnostic logging enabled, these four analytics rules provide coverage across the most important detection scenarios.

Rule 1 New Logic App with HTTP Trigger and Managed Identity

This rule fires when a Logic App is created or modified that has both an HTTP trigger (which allows external commands to initiate execution) and a managed identity. This combination is the most direct signal of the C2 pattern.

// Alert: New Logic App created with HTTP trigger and managed identity
// Priority: High
// Rationale: HTTP trigger + managed identity = potential command execution channel
AzureActivity
| where TimeGenerated > ago(24h)
| where OperationNameValue in (
    "MICROSOFT.LOGIC/WORKFLOWS/WRITE",
    "MICROSOFT.LOGIC/WORKFLOWS/ACTION"
)
| where ActivityStatusValue == "Success"
| extend ResourceDetails = parse_json(Properties)
| extend ResourceName = tostring(ResourceDetails.resource)
| extend CallerIdentity = tostring(Caller)
| extend SubscriptionId = tostring(SubscriptionId)
| project
    TimeGenerated,
    OperationNameValue,
    ResourceName,
    CallerIdentity,
    ResourceGroup,
    SubscriptionId,
    CorrelationId
| join kind=leftouter (
    // Check if the workflow has HTTP trigger in its definition
    AzureDiagnostics
    | where ResourceType == "MICROSOFT.LOGIC/WORKFLOWS"
    | where OperationName == "Microsoft.Logic/workflows/triggers/run/action"
    | where status_s == "Succeeded"
    | extend TriggerType = tostring(parse_json(tags_s).triggerType)
    | where TriggerType == "Request"  // HTTP trigger
    | distinct Resource
) on $left.ResourceName == $right.Resource
| where isnotempty(Resource)
| project TimeGenerated, ResourceName, CallerIdentity, ResourceGroup, SubscriptionId
| sort by TimeGenerated desc

Rule 2 Logic App Accessing Key Vault Outside Business Hours

Legitimate business automation workflows typically run during business hours. A Logic App accessing Key Vault at 2 AM is not necessarily malicious, but it is anomalous enough to warrant investigation — particularly if the access pattern is new.

// Alert: Logic App managed identity accessing Key Vault outside business hours
// Priority: High
// Business hours defined as 07:00-19:00 local time (adjust to your timezone)
AzureDiagnostics
| where TimeGenerated > ago(24h)
| where ResourceType == "MICROSOFT.LOGIC/WORKFLOWS"
| where OperationName has "KeyVault" or category_s has "WorkflowRuntime"
| extend Hour = hourofday(TimeGenerated)
| extend IsBusinessHours = (Hour >= 7 and Hour <= 19)
| where IsBusinessHours == false
| extend WorkflowName = tostring(resource_workflowName_s)
| extend RunId = tostring(resource_runId_s)
| extend ActionName = tostring(resource_actionName_s)
| extend Status = tostring(status_s)
| project
    TimeGenerated,
    WorkflowName,
    RunId,
    ActionName,
    Status,
    Hour,
    ResourceGroup,
    SubscriptionId
| sort by TimeGenerated desc

Rule 3 Logic App Run Accessing High-Sensitivity Resources

This rule monitors for Logic App executions that interact with Key Vaults, storage accounts containing sensitive data indicators, or other high-value resources. It correlates Logic App runs with the specific resources accessed during those runs.

// Alert: Logic App interacting with Key Vault or sensitive storage resources
// Priority: High
let SensitiveResourceTypes = dynamic([
    "Microsoft.KeyVault/vaults",
    "Microsoft.Storage/storageAccounts",
    "Microsoft.Sql/servers",
    "Microsoft.DocumentDB/databaseAccounts"
]);
AzureDiagnostics
| where TimeGenerated > ago(24h)
| where ResourceType == "MICROSOFT.LOGIC/WORKFLOWS"
| where status_s == "Succeeded"
| extend WorkflowName = tostring(resource_workflowName_s)
| extend ActionName = tostring(resource_actionName_s)
| extend ActionType = tostring(resource_actionType_s)
| extend RunId = tostring(resource_runId_s)
| where ActionType in ("ApiConnection", "Http", "Function")
| extend TargetResource = tostring(parse_json(inputs_s).uri)
| where TargetResource has "vault.azure.net"
    or TargetResource has "blob.core.windows.net"
    or TargetResource has "queue.core.windows.net"
    or TargetResource has "documents.azure.com"
| project
    TimeGenerated,
    WorkflowName,
    ActionName,
    ActionType,
    TargetResource,
    RunId,
    ResourceGroup
| join kind=leftouter (
    // Check if this Logic App had any runs in the prior 30 days
    // accessing the SAME resource — if not, flag as new access pattern
    AzureDiagnostics
    | where TimeGenerated between (ago(30d) .. ago(24h))
    | where ResourceType == "MICROSOFT.LOGIC/WORKFLOWS"
    | extend WorkflowName = tostring(resource_workflowName_s)
    | extend TargetResource = tostring(parse_json(inputs_s).uri)
    | distinct WorkflowName, TargetResource
) on WorkflowName, TargetResource
| where isempty(WorkflowName1)   // No matching history = new access pattern
| project TimeGenerated, WorkflowName, ActionName, TargetResource, RunId
| sort by TimeGenerated desc

Rule 4 Managed Identity Role Assignment to Logic App

This rule fires immediately when any managed identity — whether system-assigned to a Logic App or user-assigned and attached to one — receives a new role assignment. This is the moment the escalation is configured and the highest-value point to detect.

// Alert: Role assignment added to a managed identity associated with a Logic App
// Priority: Critical — this is when privilege escalation is configured
AzureActivity
| where TimeGenerated > ago(24h)
| where OperationNameValue == "MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE"
| where ActivityStatusValue == "Success"
| extend RoleAssignmentDetails = parse_json(Properties)
| extend RoleName = tostring(RoleAssignmentDetails.roleDefinitionName)
| extend PrincipalId = tostring(RoleAssignmentDetails.principalId)
| extend Scope = tostring(RoleAssignmentDetails.scope)
| extend AssignedBy = tostring(Caller)
| join kind=inner (
    // Join with Logic App managed identities to confirm the target is a Logic App
    Resources
    | where type == "microsoft.logic/workflows"
    | extend PrincipalId = tostring(identity.principalId)
    | where isnotempty(PrincipalId)
    | project LogicAppName = name, LogicAppRG = resourceGroup, PrincipalId
) on PrincipalId
| project
    TimeGenerated,
    LogicAppName,
    LogicAppRG,
    RoleName,
    Scope,
    AssignedBy,
    PrincipalId
| where RoleName in (
    "Owner", "Contributor", "User Access Administrator",
    "Key Vault Secrets Officer", "Key Vault Administrator",
    "Key Vault Secrets User", "Storage Blob Data Contributor",
    "Storage Account Contributor", "Virtual Machine Contributor",
    "Security Admin", "Security Reader"
)
| sort by TimeGenerated desc

Rule 5 High-Frequency Logic App Execution (Beaconing Pattern)

A Logic App running every few minutes on a fixed schedule accessing external resources is a behavioral signature of a polling C2 loop. This rule detects unusually high execution frequency compared to the prior baseline.

// Alert: Logic App executing at unusually high frequency (potential beaconing)
// Priority: Medium
let BaselinePeriod = 30d;
let AlertPeriod = 1h;
let FrequencyThreshold = 10;   // More than 10 executions per hour = alert

let Baseline = AzureDiagnostics
| where TimeGenerated > ago(BaselinePeriod)
| where ResourceType == "MICROSOFT.LOGIC/WORKFLOWS"
| where OperationName == "Microsoft.Logic/workflows/runs/action"
| extend WorkflowName = tostring(resource_workflowName_s)
| summarize BaselineHourlyRuns = count() / (BaselinePeriod / 1h) by WorkflowName;

AzureDiagnostics
| where TimeGenerated > ago(AlertPeriod)
| where ResourceType == "MICROSOFT.LOGIC/WORKFLOWS"
| where OperationName == "Microsoft.Logic/workflows/runs/action"
| extend WorkflowName = tostring(resource_workflowName_s)
| summarize RecentRuns = count() by WorkflowName
| join kind=leftouter Baseline on WorkflowName
| extend BaselineHourlyRuns = coalesce(BaselineHourlyRuns, 0.0)
| extend FrequencyIncrease = RecentRuns - BaselineHourlyRuns
| where RecentRuns > FrequencyThreshold
    or FrequencyIncrease > (BaselineHourlyRuns * 2)  // 2x baseline
| project
    WorkflowName,
    RecentRuns,
    BaselineHourlyRuns,
    FrequencyIncrease
| sort by FrequencyIncrease desc

Layer 4 The Logic Apps Sentinel Workbook

Beyond individual analytics rules, a dedicated workbook gives security teams a persistent dashboard to audit Logic App activity at a glance. The following is a complete workbook definition that can be imported directly into Sentinel.

Create a new Sentinel workbook and paste this as the JSON definition:

{
  "version": "Notebook/1.0",
  "items": [
    {
      "type": 1,
      "content": {
        "json": "# Logic Apps Security Inventory\nThis workbook surfaces all Logic Apps with managed identities, their RBAC assignments, and recent execution activity. Any Logic App you cannot explain with a known business purpose should be investigated immediately."
      }
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "Resources | where type == 'microsoft.logic/workflows' | extend identityType = tostring(identity.type) | where identityType != '' and identityType != 'None' | summarize count() by identityType",
        "title": "Logic Apps by Identity Type",
        "queryType": 1,
        "resourceType": "microsoft.resourcegraph/resources",
        "visualization": "piechart"
      }
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "AzureDiagnostics | where TimeGenerated > ago(7d) | where ResourceType == 'MICROSOFT.LOGIC/WORKFLOWS' | extend WorkflowName = tostring(resource_workflowName_s) | summarize Executions = count() by WorkflowName | top 20 by Executions desc",
        "title": "Top 20 Logic Apps by Execution Count (Last 7 Days)",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "barchart"
      }
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "AzureDiagnostics | where TimeGenerated > ago(24h) | where ResourceType == 'MICROSOFT.LOGIC/WORKFLOWS' | where status_s == 'Failed' | extend WorkflowName = tostring(resource_workflowName_s) | extend Error = tostring(parse_json(error_s).message) | project TimeGenerated, WorkflowName, Error | sort by TimeGenerated desc",
        "title": "Logic App Failures Last 24 Hours",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "table"
      }
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "AzureActivity | where TimeGenerated > ago(30d) | where OperationNameValue == 'MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE' | where ActivityStatusValue == 'Success' | extend AssignedTo = tostring(parse_json(Properties).principalId) | extend RoleName = tostring(parse_json(Properties).roleDefinitionName) | extend AssignedBy = Caller | project TimeGenerated, AssignedTo, RoleName, AssignedBy, ResourceGroup | sort by TimeGenerated desc",
        "title": "Role Assignments in Last 30 Days",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "table"
      }
    }
  ]
}

Layer 5 Key Vault Audit for Managed Identity Access

A specific query to monitor which managed identities are accessing Key Vault secrets and whether those accesses align with expected patterns:

// Key Vault access by managed identities — identify Logic App principals
AzureDiagnostics
| where TimeGenerated > ago(7d)
| where ResourceType == "MICROSOFT.KEYVAULT/VAULTS"
| where OperationName in (
    "SecretGet", "SecretList", "SecretSet",
    "KeyGet", "KeyList", "CertificateGet"
)
| extend CallerObjectId = tostring(identity_claim_oid_g)
| extend CallerAppId = tostring(identity_claim_appid_g)
| extend SecretName = tostring(id_s)
| extend ResultType = tostring(resultType_s)
| extend KeyVaultName = tostring(Resource)
| summarize
    AccessCount = count(),
    SecretsAccessed = make_set(SecretName),
    OperationsPerformed = make_set(OperationName),
    FirstAccess = min(TimeGenerated),
    LastAccess = max(TimeGenerated)
    by CallerObjectId, CallerAppId, KeyVaultName
| join kind=leftouter (
    // Identify which managed identities belong to Logic Apps
    Resources
    | where type == "microsoft.logic/workflows"
    | extend CallerObjectId = tostring(identity.principalId)
    | where isnotempty(CallerObjectId)
    | project CallerObjectId, LogicAppName = name, LogicAppRG = resourceGroup
) on CallerObjectId
| project
    CallerObjectId,
    LogicAppName,
    LogicAppRG,
    KeyVaultName,
    AccessCount,
    SecretsAccessed,
    OperationsPerformed,
    FirstAccess,
    LastAccess
| where isnotempty(LogicAppName)   // Filter to Logic App managed identities only
| order by AccessCount desc

The Remediation Playbook

Immediate Triage Steps When a Suspicious Logic App Is Found

Step 1 Disable the Logic App immediately

Do not delete it first. Disabling preserves the forensic evidence (run history, workflow definition, trigger configuration) while stopping execution.

# Disable the Logic App without deleting it
Set-AzLogicApp -ResourceGroupName "RG_NAME" -Name "LOGIC_APP_NAME" -State "Disabled"

# Verify it is disabled
Get-AzLogicApp -ResourceGroupName "RG_NAME" -Name "LOGIC_APP_NAME" | Select-Object Name, State

Step 2 Capture run history before remediation

Logic App run history contains execution details, action inputs/outputs, and timing information that is critical for understanding what the Logic App did during its operational period.

# Export run history for forensic preservation
$runs = Get-AzLogicAppRun -ResourceGroupName "RG_NAME" -Name "LOGIC_APP_NAME"
$runs | ConvertTo-Json -Depth 10 | Out-File "logicapp_runhistory_$(Get-Date -Format 'yyyyMMdd_HHmm').json"

# Get action details for each run
foreach ($run in $runs) {
    $actions = Get-AzLogicAppRunAction `
        -ResourceGroupName "RG_NAME" `
        -Name "LOGIC_APP_NAME" `
        -RunName $run.Name
    $actions | ConvertTo-Json -Depth 10 | 
        Out-File "run_$($run.Name)_actions.json"
}

Step 3 Remove managed identity role assignments

$principalId = (Get-AzLogicApp -ResourceGroupName "RG_NAME" -Name "LOGIC_APP_NAME").Identity.PrincipalId

# Get all role assignments
$assignments = Get-AzRoleAssignment -ObjectId $principalId

# Remove each assignment
foreach ($assignment in $assignments) {
    Write-Host "Removing: $($assignment.RoleDefinitionName) on $($assignment.Scope)"
    Remove-AzRoleAssignment -ObjectId $principalId `
        -RoleDefinitionName $assignment.RoleDefinitionName `
        -Scope $assignment.Scope
}

Write-Host "All role assignments removed from Logic App managed identity"

Step 4 Investigate what resources were accessed

# Query Azure Activity Log for managed identity activity
$principalId = (Get-AzLogicApp -ResourceGroupName "RG_NAME" -Name "LOGIC_APP_NAME").Identity.PrincipalId

# Get activity from last 90 days
$startTime = (Get-Date).AddDays(-90)
$events = Get-AzActivityLog -StartTime $startTime | 
    Where-Object { $_.Authorization.Principal -eq $principalId }

$events | Select-Object EventTimestamp, OperationName, ResourceId, Status | 
    Export-Csv "managed_identity_activity.csv" -NoTypeInformation

Write-Host "Activity log exported: $($events.Count) events found"

Step 5 Check for additional Logic Apps created by the same actor

# Find all Logic Apps created by the same identity that created the suspicious one
$suspiciousCreator = "CREATOR_UPN_OR_OBJECT_ID"
$startTime = (Get-Date).AddDays(-90)

$creationEvents = Get-AzActivityLog -StartTime $startTime |
    Where-Object {
        $_.Authorization.Action -eq "Microsoft.Logic/workflows/write" -and
        $_.Caller -eq $suspiciousCreator
    }

$creationEvents | Select-Object EventTimestamp, ResourceId | Format-Table

What Responders Commonly Miss

Missing Logic Apps entirely: Many incident response procedures focus on VMs, service principals, and application registrations. Logic Apps are often not in the responder’s mental checklist, and without explicit discovery queries they remain undetected.

Confusing disable with remediation: Disabling a Logic App stops execution but does not remove the managed identity or its role assignments. The managed identity token can still be acquired and used if the attacker has another foothold.

Not checking for copies: An attacker who had time to operate may have created multiple Logic Apps in different resource groups, potentially in different subscriptions. Scoping remediation to a single resource group is insufficient.

Overlooking the trigger source: HTTP-triggered Logic Apps can be invoked by anyone who has the trigger URL. That URL may have been shared with external infrastructure. Even after disabling the Logic App, the trigger URL should be treated as a compromised indicator.

Not rotating downstream credentials: If the managed identity accessed Key Vault secrets during its operation, those secrets should be rotated after the managed identity is removed. The attacker may have captured secret values during execution.


Hardening Recommendations

Beyond detection, these configuration changes reduce the attack surface for Logic App abuse:

Restrict who can create Logic Apps: By default, any user with Contributor on a resource group can create Logic Apps. Create a custom role that excludes Microsoft.Logic/workflows/write from general Contributor assignments, and require explicit justification and approval for Logic App creation.

Prohibit managed identity assignment without security review: Use Azure Policy to audit or deny managed identity assignments to Logic Apps in sensitive subscriptions. Policy definition:

{
  "if": {
    "allOf": [
      {
        "field": "type",
        "equals": "Microsoft.Logic/workflows"
      },
      {
        "field": "identity.type",
        "in": ["SystemAssigned", "UserAssigned", "SystemAssigned,UserAssigned"]
      }
    ]
  },
  "then": {
    "effect": "audit"
  }
}

Require diagnostic logging for all Logic Apps: Use Azure Policy to enforce diagnostic settings on all Logic Apps, ensuring execution logs flow to your Sentinel workspace by default rather than requiring manual configuration per app.

Apply network restrictions to Logic Apps with HTTP triggers: Standard Logic Apps can be configured with IP-based access restrictions on their HTTP triggers. Restrict the allowed source IPs to known internal ranges only, blocking external trigger invocations.

Review Logic App permissions quarterly: Add Logic App managed identity role assignments to your quarterly access review process alongside service principal and application registration reviews.


Conclusion

The most dangerous attacker in your cloud is one who looks exactly like your DevOps team. They are not triggering malware signatures. They are not making connections from suspicious IPs. They are running automation workflows that blend perfectly with your legitimate business operations. Their persistence mechanism is not on any endpoint — it is in your Azure portal, in a resource group nobody looks at, running every five minutes and generating no alerts.

The good news is that detection is achievable. Azure provides the logging infrastructure. Sentinel provides the analytics platform. The queries in this article surface the behaviors that distinguish malicious Logic App usage from legitimate workflows. The gap is not technical capability — it is awareness.

Most organizations have invested significant resources in identity security, endpoint protection, and network monitoring. Almost none have invested equivalent resources in monitoring their own automation infrastructure. That asymmetry is exactly what makes this technique attractive to sophisticated actors and exactly why closing it matters.

Build the inventory. Enable the logging. Deploy the detection rules. Audit the managed identity role assignments. The attacker who has already done their reconnaissance is counting on the fact that you have not done any of these things yet.


References

ResourceLink
MITRE ATT&CK — T1648: Serverless Executionhttps://attack.mitre.org/techniques/T1648/
MITRE ATT&CK — T1098.003: Additional Cloud Credentialshttps://attack.mitre.org/techniques/T1098/003/
MITRE ATT&CK — T1078.004: Valid Accounts: Cloud Accountshttps://attack.mitre.org/techniques/T1078/004/
Microsoft — Azure Logic Apps Security Baselinehttps://learn.microsoft.com/security/benchmark/azure/baselines/logic-apps-security-baseline
Microsoft — Monitor Logic Apps with Diagnosticshttps://learn.microsoft.com/azure/logic-apps/monitor-logic-apps
Microsoft — Managed Identity for Logic Appshttps://learn.microsoft.com/azure/logic-apps/create-managed-service-identity
Microsoft Digital Defense Report 2025https://www.microsoft.com/security/business/microsoft-digital-defense-report
Azure Logic Apps Diagnostic Logs Schemahttps://learn.microsoft.com/azure/azure-monitor/reference/tables/azurediagnostics
CISA — Secure Cloud Business Applications (SCuBA)https://www.cisa.gov/resources-tools/services/secure-cloud-business-applications-scuba-project
Azure Policy — Built-in Definitions for Logic Appshttps://learn.microsoft.com/azure/logic-apps/policy-reference
Microsoft Sentinel — Azure Activity Connectorhttps://learn.microsoft.com/azure/sentinel/data-connectors/azure-activity
KQL Reference — AzureDiagnostics Tablehttps://learn.microsoft.com/azure/azure-monitor/reference/tables/azurediagnostics