This issue has been assessed as high severity. Review affected configurations immediately.
Azure Logic Apps is one of those services that accumulates security debt quietly. Developers build integrations quickly, attach a managed identity for authentication, and move on. The managed identity gets scoped to Contributor on the subscription because it’s easier than figuring out the minimum required roles. The HTTP trigger has no authentication because the developer planned to add it later. The workflow definition includes a hardcoded connection string because the Key Vault integration was going to be done in the next sprint.
This is the typical pattern, and it creates consistent, exploitable privilege escalation paths.
How Logic Apps Fits Into Azure Attack Chains
A Logic App executes with the permissions of its system-assigned or user-assigned managed identity. If that identity has broad roles — Contributor, Owner, or any data-plane roles like Storage Blob Data Contributor at the subscription level — anyone who can trigger the workflow can exercise those permissions indirectly.
The attack model: an attacker who discovers an unauthenticated HTTP trigger can send a crafted request that causes the Logic App to take actions under its managed identity. If the identity can write to storage, the attacker writes to storage. If it can invoke Azure Resource Manager operations, the attacker can modify resource configurations. If it has Contributor on the subscription, the attacker effectively has Contributor on the subscription.
Finding Overprivileged Logic App Identities
Enumerate Logic Apps with System-Assigned Identities
# List all Logic Apps with system-assigned managed identity
az logic workflow list --query \
"[?identity.type=='SystemAssigned'].{name:name, rg:resourceGroup, identityId:identity.principalId}" \
--output table
Check RBAC Assignments for Each Identity
# For each principal ID from above, check role assignments
az role assignment list \
--assignee <principal-id> \
--query "[].{role:roleDefinitionName, scope:scope}" \
--output table
High-risk findings:
OwnerorContributorat subscription scope (/subscriptions/<id>)Storage Blob Data OwnerorStorage Blob Data Contributorat storage account scopeKey Vault AdministratororKey Vault Secrets Officer- Any custom role with
Microsoft.Authorization/*/write— privilege escalation via role assignment
Check for Logic Apps Accessible Without Authentication
Logic App HTTP triggers can be configured with No Authentication, Basic, or managed identity authentication. The trigger URL itself contains a SAS token by default, but SAS tokens rotate only on manual regeneration or when the workflow is modified.
# Get the trigger URL for a Logic App (contains embedded SAS if auth=off)
az logic workflow trigger list \
--resource-group <rg> \
--workflow-name <logic-app-name> \
--query "[].name" \
--output tsv | while read trigger; do
az rest --method POST \
--uri "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Logic/workflows/<name>/triggers/$trigger/listCallbackUrl?api-version=2016-06-01" \
--query "value" --output tsv
done
A trigger URL containing sp= and sv= parameters in the query string is using SAS-based authentication. The URL itself is a secret — if it appears in application logs, browser history, or is shared between teams, the trigger is accessible to anyone with the URL.
Workflow Definition Secrets
Logic App definitions are JSON documents stored in ARM and visible to anyone with Microsoft.Logic/workflows/read access. Connection credentials embedded in workflow definitions are a common finding.
# Download workflow definition and look for embedded credentials
az logic workflow show \
--resource-group <rg> \
--name <name> \
--query "definition" > workflow_def.json
# Patterns to grep for
grep -i -E "(password|connectionString|apiKey|token|secret|SharedAccessKey)" workflow_def.json
The correct pattern is to use Key Vault references or managed identity authentication for all connections. Connection strings embedded in workflow definitions are visible to all users with read access to the Logic App and are stored in Azure Resource Manager history.
Hardening
1. Minimum-Scope Managed Identity Roles
Assign the managed identity only the roles required for its specific workflow tasks:
# Example: scope to a specific storage container, not the full account
az role assignment create \
--assignee <principal-id> \
--role "Storage Blob Data Reader" \
--scope "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<account>/blobServices/default/containers/<container>"
Avoid Owner, Contributor, or User Access Administrator at any scope above the specific resource the Logic App needs to access.
2. Enable Azure AD Authentication on HTTP Triggers
Replace SAS-authenticated triggers with Azure AD-authenticated triggers where possible. In the Logic App HTTP trigger settings, set Auth Type to Active Directory OAuth, and configure the Logic App to accept tokens from a specific client application.
For internal triggers, consider using managed identity authentication: the calling service authenticates as itself, and Logic App validates the token.
3. Store Connection Credentials in Key Vault
Reference Key Vault secrets in connection configurations rather than embedding credentials in workflow definitions:
{
"connectionString": "@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/MyConnectionString/)"
}
This requires the Logic App’s managed identity to have Key Vault Secrets User on the specific secret.
4. Audit with Microsoft Defender for Cloud
Enable the Logic Apps security policy in Microsoft Defender for Cloud. The Logic Apps workflows should use managed identities for authentication policy recommendation flags workflows using connection string-based authentication.
Detection
In Microsoft Sentinel, alert on Logic App trigger invocations from unexpected source IPs or user agents:
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.LOGIC"
| where OperationName == "Microsoft.Logic/workflows/triggers/run/action"
| where status_s == "Succeeded"
| where CallerIpAddress !in (expected_ip_ranges)
| summarize TriggerCount = count() by CallerIpAddress, WorkflowName = resource_workflowName_s, bin(TimeGenerated, 1h)
| where TriggerCount > 10
Alert on any changes to Logic App identity configuration:
AzureActivity
| where OperationNameValue == "MICROSOFT.LOGIC/WORKFLOWS/WRITE"
| where Properties contains "identity"
| project TimeGenerated, Caller, ResourceGroup, Resource, Properties