Identifying Security Weakness in Function Apps and Storage Accounts

BLUF: If an adversary can List Keys on a storage account that contains Azure Function, they can already modify the Azure Function because the only person that can List Keys is the owner of the Storage Account and any inherited owners.

I recently had a customer ask to get a better understanding of a potential security vulnerability in the default behavior of Azure Functions and their use of Storage Accounts. The following blog will discuss the potential weakness with the default settings and how to identify, remediate, and prevent them.

But first, let me caveat with saying an adversary would need to have more than read access to a Storage Account to use this weakness to their advantage. This blog is driven by an article published by The Hacker News, Newly Discovered "By-Design" Flaw in Microsoft Azure Could Expose Storage Accounts to Hackers (thehackernews.com). Microsoft has addressed this in the following blog: Best practices regarding Azure Storage Keys, Azure Functions, and Azure Role Based Access | MSRC Blog | Microsoft Security Response Center

Problem: The current issue concerns potential security vulnerabilities within Azure storage accounts. If an adversary gains permission to list keys of a storage account, they may access Azure Functions stored within that account and modify the code to escalate privileges and facilitate lateral movement within an organization's cloud resources. The default generation of shared access keys when a storage account is created exacerbates this issue. The challenge is to identify vulnerable storage accounts and reduce the risk of using shared access keys.

The following are phases to address this potential issue:

Identification:

  1. Different methods to determine which Storage Accounts are using Shared Key

    1. Using Azure Policy: Configure the Azure Policy for Shared Key access in audit mode
    2. Using Azure Resource Graph Explorer
      1. Query:

    resources
    | where type =~ 'Microsoft.Storage/storageAccounts'
    | extend allowSharedKeyAccess = parse_json(properties).allowSharedKeyAccess
    | where isnull(allowSharedKeyAccess) or allowSharedKeyAccess == true
    | project subscriptionId, resourceGroup, name, allowSharedKeyAccess

  2. List Function Apps

    1. Using Azure Resource Graph Explorer
      1. Query:

    resources
    | where type == "microsoft.web/sites"
    | where ['kind'] contains "functionapp"

  3. Determine which the Storage Account for each Azure Function

    1. Go to the Azure Function in the Portal
    2. Select Configuration
    3. Unhide the value for AzureWebJobsStorage
    4. Storage Account = "DefaultEndpointsProtocol=https;AccountName=STORAGEACCOUNTNAME"

If both are true for the Storage Account (Shared Key Access and Azure Function), these are the higher-risk Storage Accounts.

The following PowerShell script, combined with the Azure Command line, will produce a list of Azure Functions, their Storage Accounts, and if AllowSharedKeyAccess is enabled. I found it easier to run in Azure Cloud Shell. If you do run it there, you don't need the "Connect-AzAccount".

# Log in to your Azure account
Connect-AzAccount

# Get all resource groups in your subscription
$resourceGroups = Get-AzResourceGroup

foreach ($resourceGroup in $resourceGroups) {
# Get all web apps in the resource group
$webApps = Get-AzWebApp -ResourceGroupName $resourceGroup.ResourceGroupName

foreach ($webApp in $webApps) {
    # Check if the web app is a function app
    if ($webApp.Kind -like '*functionapp*') {
        # Get the function app settings
        $storageSetting = az functionapp config appsettings list --name $webApp.Name --resource-group $resourceGroup.ResourceGroupName | ConvertFrom-Json | Where-Object name -eq 'AzureWebJobsStorage'

        # Extract the account name from the setting
        $accountName = $storageSetting -Split ";" | Where-Object { $_.StartsWith('AccountName') } | ForEach-Object { $_.Split('=')[1] }

        #Check if storage account allow shared key access
        $sharedkeyaccess = Get-AzStorageAccount -Name $accountName -ResourceGroupName $resourceGroup.ResourceGroupName  | Select-Object -expandproperty AllowSharedkeyAccess
        if($sharedkeyaccess -eq $null){$sharedkeyaccess = "null"}

        Write-Output "Function App: $($webApp.Name), Storage Account: $accountName, Shared Key Access: $sharedkeyaccess"
    }
}

}

Remediation:

  1. Determine if applications that use these Azure Functions will be impacted by disabling Shared Key access
    a. Detect the type of authorization used by client applications
  2. If true, remediate the applications to use Azure AD to authorize requests against the Storage Account
    a. https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication
  3. The following article contains guidance on securing Azure Functions. Like using Key Vault.
    a. Securing Azure Functions | Microsoft Learn
  4. If false, you should be safe to disable Shared Key access
    a. https://learn.microsoft.com/en-us/azure/storage/common/shared-key-authorization-prevent

Prevention: Prevent Shared Key authorization for an Azure Storage Account