# Az - Function Apps

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

## Informations de base

**Azure Function Apps** sont un **service de calcul sans serveur** qui vous permet d'exécuter de petits morceaux de code, appelés **fonctions**, sans gérer l'infrastructure sous-jacente. Ils sont conçus pour exécuter du code en réponse à divers déclencheurs, tels que **des requêtes HTTP, des minuteries ou des événements d'autres services Azure** comme Blob Storage ou Event Hubs. Les Function Apps prennent en charge plusieurs langages de programmation, y compris C#, Python, JavaScript et Java, ce qui les rend polyvalents pour créer des **applications basées sur des événements**, automatiser des flux de travail ou intégrer des services. Ils sont rentables, car vous ne payez généralement que pour le temps de calcul utilisé lorsque votre code s'exécute.

> [!NOTE]
> Notez que **les fonctions sont un sous-ensemble des App Services**, par conséquent, de nombreuses fonctionnalités discutées ici seront également utilisées par les applications créées en tant qu'Azure Apps (`webapp` dans cli).

### Différents Plans

- **Flex Consumption Plan** : Offre un **scalabilité dynamique et basée sur les événements** avec un tarif à l'utilisation, ajoutant ou supprimant des instances de fonction en fonction de la demande. Il prend en charge **le réseau virtuel** et **les instances pré-provisionnées** pour réduire les démarrages à froid, ce qui le rend adapté aux **charges de travail variables** qui ne nécessitent pas de support de conteneur.
- **Traditional Consumption Plan** : L'option sans serveur par défaut, où vous **ne payez que pour les ressources de calcul lorsque les fonctions s'exécutent**. Il s'adapte automatiquement en fonction des événements entrants et inclut des **optimisations de démarrage à froid**, mais ne prend pas en charge les déploiements de conteneurs. Idéal pour les **charges de travail intermittentes** nécessitant une scalabilité automatique.
- **Premium Plan** : Conçu pour des **performances constantes**, avec des **travailleurs préchauffés** pour éliminer les démarrages à froid. Il offre **des temps d'exécution prolongés, un réseau virtuel**, et prend en charge **des images Linux personnalisées**, ce qui le rend parfait pour des **applications critiques** nécessitant des performances élevées et des fonctionnalités avancées.
- **Dedicated Plan** : Fonctionne sur des machines virtuelles dédiées avec une **facturation prévisible** et prend en charge la scalabilité manuelle ou automatique. Il permet d'exécuter plusieurs applications sur le même plan, fournit **une isolation de calcul**, et garantit un **accès réseau sécurisé** via des environnements de service d'application, ce qui le rend idéal pour des **applications de longue durée** nécessitant une allocation de ressources constante.
- **Container Apps** : Permet de déployer des **applications de fonction conteneurisées** dans un environnement géré, aux côtés de microservices et d'APIs. Il prend en charge des bibliothèques personnalisées, la migration d'applications héritées, et le **traitement GPU**, éliminant la gestion des clusters Kubernetes. Idéal pour des **applications conteneurisées évolutives basées sur des événements**.

### **Buckets de stockage**

Lors de la création d'une nouvelle Function App non conteneurisée (mais en fournissant le code à exécuter), le **code et d'autres données liées à la fonction seront stockés dans un compte de stockage**. Par défaut, la console web créera un nouveau compte par fonction pour stocker le code.

De plus, en modifiant le code à l'intérieur du bucket (dans les différents formats dans lesquels il pourrait être stocké), le **code de l'application sera modifié pour le nouveau et exécuté** la prochaine fois que la fonction est appelée.

> [!CAUTION]
> Cela est très intéressant du point de vue d'un attaquant car **l'accès en écriture sur ce bucket** permettra à un attaquant de **compromettre le code et d'escalader les privilèges** aux identités gérées à l'intérieur de la Function App.
>
> Plus d'informations à ce sujet dans la **section sur l'escalade de privilèges**.

Il est également possible de trouver les **clés maître et fonctions** 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.

Notez que les fonctions permettent également de stocker le code dans un emplacement distant en indiquant simplement l'URL.

### Réseautage

En utilisant un déclencheur HTTP :

- Il est possible de donner **accès à une fonction depuis tout Internet** sans nécessiter d'authentification ou de donner un accès basé sur IAM. Bien qu'il soit également possible de restreindre cet accès.
- Il est également possible de **donner ou restreindre l'accès** à une Function App depuis **un réseau interne (VPC)**.

> [!CAUTION]
> Cela est très intéressant du point de vue d'un attaquant car il pourrait être possible de **pivoter vers des réseaux internes** depuis une fonction vulnérable exposée à Internet.

### **Paramètres de l'application de fonction et variables d'environnement**

Il est possible de configurer des variables d'environnement à l'intérieur d'une application, qui pourraient contenir des informations sensibles. De plus, par défaut, les variables d'environnement **`AzureWebJobsStorage`** et **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** (entre autres) sont créées. Celles-ci sont particulièrement intéressantes car elles **contiennent la clé de compte pour contrôler avec des permissions COMPLETES le compte de stockage contenant les données de l'application**. Ces paramètres sont également nécessaires pour exécuter le code depuis le compte de stockage.

Ces variables d'environnement ou paramètres de configuration contrôlent également comment la fonction exécute le code, par exemple si **`WEBSITE_RUN_FROM_PACKAGE`** existe, cela indiquera l'URL où le code de l'application est situé.

### **Sandbox de fonction**

À l'intérieur de la sandbox linux, le code source est situé dans **`/home/site/wwwroot`** dans le fichier **`function_app.py`** (si Python est utilisé) l'utilisateur exécutant le code est **`app`** (sans permissions sudo).

Dans une fonction **Windows** utilisant NodeJS, le code était situé dans **`C:\home\site\wwwroot\HttpTrigger1\index.js`**, le nom d'utilisateur était **`mawsFnPlaceholder8_f_v4_node_20_x86`** et faisait partie des **groupes** : `Mandatory Label\High Mandatory Level Label`, `Everyone`, `BUILTIN\Users`, `NT AUTHORITY\INTERACTIVE`, `CONSOLE LOGON`, `NT AUTHORITY\Authenticated Users`, `NT AUTHORITY\This Organization`, `BUILTIN\IIS_IUSRS`, `LOCAL`, `10-30-4-99\Dwas Site Users`.

### **Identités gérées et métadonnées**

Tout comme les [**VMs**](vms/index.html), les fonctions peuvent avoir des **Identités Gérées** de 2 types : Assignée au système et Assignée à l'utilisateur.

L'identité **assignée au système** sera une identité gérée que **seule la fonction** qui l'a assignée pourra utiliser, tandis que les identités gérées **assignées à l'utilisateur** sont des identités gérées que **tout autre service Azure pourra utiliser**.

> [!NOTE]
> Tout comme dans les [**VMs**](vms/index.html), les fonctions peuvent avoir **1 identité gérée assignée au système** et **plusieurs assignées à l'utilisateur**, il est donc toujours important d'essayer de trouver toutes celles-ci si vous compromettez la fonction car vous pourriez être en mesure d'escalader les privilèges vers plusieurs identités gérées à partir d'une seule fonction.
>
> Si aucune identité gérée au système n'est utilisée mais qu'une ou plusieurs identités gérées par l'utilisateur sont attachées à une fonction, par défaut, vous ne pourrez pas obtenir de jeton.

Il est possible d'utiliser les [**scripts PEASS**](https://github.com/peass-ng/PEASS-ng) pour obtenir des jetons de l'identité gérée par défaut à partir du point de terminaison des métadonnées. Ou vous pourriez les obtenir **manuellement** comme expliqué dans :

{% embed url="https://book.hacktricks.wiki/en/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf.html#azure-vm" %}

Notez que vous devez trouver un moyen de **vérifier toutes les identités gérées qu'une fonction a attachées** car si vous ne l'indiquez pas, le point de terminaison des métadonnées **n'utilisera que la par défaut** (voir le lien précédent pour plus d'informations).

## Clés d'accès

> [!NOTE]
> Notez qu'il n'y a pas de permissions RBAC pour donner accès aux utilisateurs pour invoquer les fonctions. L'**invocation de la fonction dépend du déclencheur** sélectionné lors de sa création et si un déclencheur HTTP a été sélectionné, il pourrait être nécessaire d'utiliser une **clé d'accès**.

Lors de la création d'un point de terminaison à l'intérieur d'une fonction utilisant un **déclencheur HTTP**, il est possible d'indiquer le **niveau d'autorisation de clé d'accès** nécessaire pour déclencher la fonction. Trois options sont disponibles :

- **ANONYMOUS** : **Tout le monde** peut accéder à la fonction par l'URL.
- **FUNCTION** : Le point de terminaison n'est accessible qu'aux utilisateurs utilisant une **clé de fonction, d'hôte ou maître**.
- **ADMIN** : Le point de terminaison n'est accessible qu'aux utilisateurs avec une **clé maître**.

**Type de clés :**

- **Clés de fonction :** Les clés de fonction peuvent être soit par défaut soit définies par l'utilisateur et sont conçues pour accorder un accès exclusivement à **des points de terminaison de fonction spécifiques** au sein d'une Function App permettant un accès plus granulaire sur les points de terminaison.
- **Clés d'hôte :** Les clés d'hôte, qui peuvent également être par défaut ou définies par l'utilisateur, fournissent un accès à **tous les points de terminaison de fonction au sein d'une Function App avec un niveau d'accès FUNCTION**.
- **Clé maître :** La clé maître (`_master`) sert de clé administrative qui offre des permissions élevées, y compris l'accès à tous les points de terminaison de fonction (niveau d'accès ADMIN inclus). Cette **clé ne peut pas être révoquée.**
- **Clés système :** Les clés système sont **gérées par des extensions spécifiques** et sont nécessaires pour accéder aux points de terminaison webhook utilisés par des composants internes. Des exemples incluent le déclencheur Event Grid et les Durable Functions, qui utilisent des clés système pour interagir de manière sécurisée avec leurs API respectives.

> [!TIP]
> Exemple pour accéder à un point de terminaison API de fonction en utilisant une clé :
>
> `https://<function_uniq_name>.azurewebsites.net/api/<endpoint_name>?code=<access_key>`

### Authentification de base

Tout comme dans les App Services, les fonctions prennent également en charge l'authentification de base pour se connecter à **SCM** et **FTP** pour déployer du code en utilisant un **nom d'utilisateur et un mot de passe dans une URL** fournie par Azure. Plus d'informations à ce sujet dans :

{{#ref}}
az-app-services.md
{{#endref}}

### Déploiements basés sur Github

Lorsqu'une fonction est générée à partir d'un dépôt Github, la console web Azure permet de **créer automatiquement un Workflow Github dans un dépôt spécifique** afin que chaque fois que ce dépôt est mis à jour, le code de la fonction soit mis à jour. En fait, le fichier yaml de l'action Github pour une fonction Python ressemble à ceci :

<details>

<summary>Yaml d'action Github</summary>
```yaml
# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action
# More GitHub Actions for Azure: https://github.com/Azure/actions
# More info on Python, GitHub Actions, and Azure Functions: https://aka.ms/python-webapps-actions

name: Build and deploy Python project to Azure Function App - funcGithub

on:
push:
branches:
- main
workflow_dispatch:

env:
AZURE_FUNCTIONAPP_PACKAGE_PATH: "." # set this to the path to your web app project, defaults to the repository root
PYTHON_VERSION: "3.11" # set this to the python version to use (supports 3.6, 3.7, 3.8)

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Python version
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Create and start virtual environment
run: |
python -m venv venv
source venv/bin/activate

- name: Install dependencies
run: pip install -r requirements.txt

# Optional: Add step to run tests here

- name: Zip artifact for deployment
run: zip release.zip ./* -r

- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
with:
name: python-app
path: |
release.zip
!venv/

deploy:
runs-on: ubuntu-latest
needs: build

permissions:
id-token: write #This is required for requesting the JWT

steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: python-app

- name: Unzip artifact for deployment
run: unzip release.zip

- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_6C3396368D954957BC58E4C788D37FD1 }}
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_7E50AEF6222E4C3DA9272D27FB169CCD }}
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_905358F484A74277BDC20978459F26F4 }}

- name: "Deploy to Azure Functions"
uses: Azure/functions-action@v1
id: deploy-to-function
with:
app-name: "funcGithub"
slot-name: "Production"
package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}
```
</details>

De plus, une **Managed Identity** est également créée afin que l'Action Github du dépôt puisse se connecter à Azure avec elle. Cela se fait en générant une crédential fédérée sur la **Managed Identity** permettant à l'**Issuer** `https://token.actions.githubusercontent.com` et à l'**Subject Identifier** `repo:<org-name>/<repo-name>:ref:refs/heads/<branch-name>`.

> [!CAUTION]
> Par conséquent, quiconque compromettant ce dépôt pourra compromettre la fonction et les Managed Identities qui y sont attachées.

### Déploiements Basés sur des Conteneurs

Tous les plans ne permettent pas de déployer des conteneurs, mais pour ceux qui le font, la configuration contiendra l'URL du conteneur. Dans l'API, le paramètre **`linuxFxVersion`** aura quelque chose comme : `DOCKER|mcr.microsoft.com/...`, tandis que dans la console web, la configuration affichera les **image settings**.

De plus, **aucun code source ne sera stocké dans le compte de stockage** lié à la fonction car cela n'est pas nécessaire.

## Énumération

{{#tabs }}
{{#tab name="az cli" }}
```bash
# List all the functions
az functionapp list

# Get info of 1 funciton (although in the list you already get this info)
az functionapp show --name <app-name> --resource-group <res-group>
## If "linuxFxVersion" has something like: "DOCKER|mcr.microsoft.com/..."
## This is using a container

# Get details about the source of the function code
az functionapp deployment source show \
--name <app-name> \
--resource-group <res-group>
## If error like "This is currently not supported."
## Then, this is probalby using a container

# Get more info if a container is being used
az functionapp config container show \
--name <name> \
--resource-group <res-group>

# Get settings (and privesc to the sorage account)
az functionapp config appsettings list --name <app-name> --resource-group <res-group>

# Check if a domain was assigned to a function app
az functionapp config hostname list --webapp-name <app-name> --resource-group <res-group>

# Get SSL certificates
az functionapp config ssl list --resource-group <res-group>

# Get network restrictions
az functionapp config access-restriction show --name <app-name> --resource-group <res-group>

# Get more info about a function (invoke_url_template is the URL to invoke and script_href allows to see the code)
az rest --method GET \
--url "https://management.azure.com/subscriptions/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/functions?api-version=2024-04-01"

# Get source code with Master Key of the function
curl "<script_href>?code=<master-key>"
## Python example
curl "https://newfuncttest123.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py?code=<master-key>" -v

# Get source code
az rest --url "https://management.azure.com/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/hostruntime/admin/vfs/function_app.py?relativePath=1&api-version=2022-03-01"
```
{{#endtab }}

{{#tab name="Az Powershell" }}
```powershell
Get-Command -Module Az.Functions

# Lists all Function Apps in the current subscription or in a specific resource group.
Get-AzFunctionApp  -ResourceGroupName <String>

# Displays the regions where Azure Function Apps are available for deployment.
Get-AzFunctionAppAvailableLocation

# Retrieves details about Azure Function App plans in a subscription or resource group.
Get-AzFunctionAppPlan -ResourceGroupName <String> -Name <String>

# Retrieves the app settings for a specific Azure Function App.
Get-AzFunctionAppSetting -Name <FunctionAppName> -ResourceGroupName <ResourceGroupName>
```
{{#endtab }}
{{#endtabs }}


## Escalade de privilèges

{{#ref}}
../az-privilege-escalation/az-functions-app-privesc.md
{{#endref}}

## Références

- [https://learn.microsoft.com/en-us/azure/azure-functions/functions-openapi-definition](https://learn.microsoft.com/en-us/azure/azure-functions/functions-openapi-definition)

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