# Az - Function Apps

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

## Basic Information

**Azure Function Apps** are a **serverless compute service** that allow you to run small pieces of code, called **functions**, without managing the underlying infrastructure. They are designed to execute code in response to various triggers, such as **HTTP requests, timers, or events from other Azure services** like Blob Storage or Event Hubs. Function Apps support multiple programming languages, including C#, Python, JavaScript, and Java, making them versatile for building **event-driven applications**, automating workflows, or integrating services. They are cost-effective, as you usually only pay for the compute time used when your code runs.

> [!NOTE]
> Note that **Functions are a subset of the App Services**, therefore, a lot of the features discussed here will be used also by applications created as Azure Apps (`webapp` in cli).

### Different Plans

- **Flex Consumption Plan**: Offers **dynamic, event-driven scaling** with pay-as-you-go pricing, adding or removing function instances based on demand. It supports **virtual networking** and **pre-provisioned instances** to reduce cold starts, making it suitable for **variable workloads** that don’t require container support.
- **Traditional Consumption Plan**: The default serverless option, where you **pay only for compute resources when functions run**. It automatically scales based on incoming events and includes **cold start optimizations**, but does not support container deployments. Ideal for **intermittent workloads** requiring automatic scaling.
- **Premium Plan**: Designed for **consistent performance**, with **prewarmed workers** to eliminate cold starts. It offers **extended execution times, virtual networking**, and supports **custom Linux images**, making it perfect for **mission-critical applications** needing high performance and advanced features.
- **Dedicated Plan**: Runs on dedicated virtual machines with **predictable billing** and supports manual or automatic scaling. It allows running multiple apps on the same plan, provides **compute isolation**, and ensures **secure network access** via App Service Environments, making it ideal for **long-running applications** needing consistent resource allocation.
- **Container Apps**: Enables deploying **containerized function apps** in a managed environment, alongside microservices and APIs. It supports custom libraries, legacy app migration, and **GPU processing**, eliminating Kubernetes cluster management. Ideal for **event-driven, scalable containerized applications**.

### **Storage Buckets**

When creating a new Function App not containerised (but giving the code to run), the **code and other Function related data will be stored in a Storage account**. By default the web console will create a new one per function to store the code.

Moreover, modifying the code inside the bucket (in the different formats it could be stored), the **code of the app will be modified to the new one and executed** next time the Function is called.

> [!CAUTION]
> This is very interesting from an attackers perspective as **write access over this bucket** will allow an attacker to **compromise the code and escalate privileges** to the managed identities inside the Function App.
>
> More on this in the **privilege escalation section**.

It's also possible to find the **master and functions keys** stored in the storage account in the container **`azure-webjobs-secrets`** inside the folder **`<app-name>`** in the JSON files you can find inside.

Note that Functions also allow to store the code in a remote location just indicating the URL to it.

### Networking

Using a HTTP trigger:

- It's possible to give **access to a function to from all Internet** without requiring any authentication or give access IAM based. Although it’s also possible to restrict this access.
- It's also possible to **give or restrict access** to a Function App from **an internal network (VPC)**.

> [!CAUTION]
> This is very interesting from an attackers perspective as it might be possible to **pivot to internal networks** from a vulnerable Function exposed to the Internet.

### **Function App Settings & Environment Variables**

It's possible to configure environment variables inside an app, which could contain sensitive information. Moreover, by default the env variables **`AzureWebJobsStorage`** and **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** (among others) are created. These are specially interesting because they **contain the account key to control with FULL permissions the storage account containing the data of the application**. These settings are also needed to execute the code from the Storage Account.

These env variables or configuration parameters also controls how the Function execute the code, for example if **`WEBSITE_RUN_FROM_PACKAGE`** exists, it'll indicate the URL where the code of the application is located.

### **Function Sandbox**

Inside the linux sandbox the source code is located in **`/home/site/wwwroot`** in the file **`function_app.py`** (if python is used) the user running the code is **`app`** (without sudo permissions).

In a **Windows** function using NodeJS the code was located in **`C:\home\site\wwwroot\HttpTrigger1\index.js`**, the username was **`mawsFnPlaceholder8_f_v4_node_20_x86`** and was part of the **groups**: `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`.

### **Managed Identities & Metadata**

Just like [**VMs**](vms/index.html), Functions can have **Managed Identities** of 2 types: System assigned and User assigned.

The **system assigned** one will be a managed identity that **only the function** that has it assigned would be able to use, while the **user assigned** managed identities are managed identities that **any other Azure service will be able to use**.

> [!NOTE]
> Just like in [**VMs**](vms/index.html), Functions can have **1 system assigned** managed identity and **several user assigned** ones, so it's always important to try to find all of them if you compromise the function because you might be able to escalate privileges to several managed identities from just one Function.
>
> If a no system managed identity is used but one or more user managed identities are attached to a function, by default you won’t be able to get any token.

It's possible to use the [**PEASS scripts**](https://github.com/peass-ng/PEASS-ng) to get tokens from the default managed identity from the metadata endpoint. Or you could get them **manually** as explained in:

{{#ref}}
https://book.hacktricks.wiki/en/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf.html#azure-vm
{{#endref}}

Note that you need to find out a way to **check all the Managed Identities a function has attached** as if you don't indicate it, the metadata endpoint will **only use the default one** (check the previous link for more info).

## Access Keys

> [!NOTE]
> Note that there aren't RBAC permissions to give access to users to invoke the functions. The **function invocation depends on the trigger** selected when it was created and if a HTTP Trigger was selected, it might be needed to use an **access key**.

When creating an endpoint inside a function using a **HTTP trigger** it's possible to indicate the **access key authorization level** needed to trigger the function. Three options are available:

- **ANONYMOUS**: **Everyone** can access the function by the URL.
- **FUNCTION**: Endpoint is only accessible to users using a **function, host or master key**.
- **ADMIN**: Endpoint is only accessible to users a **master key**.

**Type of keys:**

- **Function Keys:** Function keys can be either default or user-defined and are designed to grant access exclusively to **specific function endpoints** within a Function App allowing a more fine-grained access over the endpoints.
- **Host Keys:** Host keys, which can also be default or user-defined, provide access to **all function endpoints within a Function App with FUNCTION access level**.
- **Master Key:** The master key (`_master`) serves as an administrative key that offers elevated permissions, including access to all function endpoints (ADMIN access lelvel included). This **key cannot be revoked.**
- **System Keys:** System keys are **managed by specific extensions** and are required for accessing webhook endpoints used by internal components. Examples include the Event Grid trigger and Durable Functions, which utilize system keys to securely interact with their respective APIs.

> [!TIP]
> Example to access a function API endpoint using a key:
>
> `https://<function_uniq_name>.azurewebsites.net/api/<endpoint_name>?code=<access_key>`

### Basic Authentication

Just like in App Services, Functions also support basic authentication to connect to **SCM** and **FTP** to deploy code using a **username and password in a URL** provided by Azure. More information about it in:

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

### Github Based Deployments

When a function is generated from a Github repo Azure web console allows to **automatically create a Github Workflow in a specific repository** so whenever this repository is updated the code of the function is updated. Actually the Github Action yaml for a python function looks like this:

<details>

<summary>Github Action Yaml</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>

Moreover, a **Managed Identity** is also created so the Github Action from the repository will be able to login into Azure with it. This is done by generating a Federated credential over the **Managed Identity** allowing the **Issuer** `https://token.actions.githubusercontent.com` and the **Subject Identifier** `repo:<org-name>/<repo-name>:ref:refs/heads/<branch-name>`.

> [!CAUTION]
> Therefore, anyone compromising that repo will be able to compromise the function and the Managed Identities attached to it.

### Container Based Deployments

Not all the plans allow to deploy containers, but for the ones that do, the configuration will contain the URL of the container. In the API the **`linuxFxVersion`** setting will ha something like: `DOCKER|mcr.microsoft.com/...`, while in the web console, the configuration will show the **image settings**.

Moreover, **no source code will be stored in the storage** account related to the function as it's not needed.

## Enumeration

{{#tabs }}
{{#tab name="az cli" }}

```bash
# List all the functions
az functionapp list

# List functions in an function-app (endpoints)
az functionapp function list \
  --name <app-name> \
  --resource-group <res-group>

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

# Get access restrictions
az functionapp config access-restriction show --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 acess restrictions
az functionapp config access-restriction show --name <app-name> --resource-group <res-group>

# Get connection strings
az rest --method POST --uri "https://management.azure.com/subscriptions/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/config/connectionstrings/list?api-version=2022-03-01"
az rest --method GET --uri "https://management.azure.com/subscriptions/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/config/configreferences/connectionstrings?api-version=2022-03-01"

# Get SCM credentials
az functionapp deployment list-publishing-credentials --name <app-name> --resource-group <res-group>

# Get function, system and master keys
az functionapp keys list --name <app-name> --resource-group <res-group>

# Get Host key
az rest --method POST --uri "https://management.azure.com/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/functions/<function-endpoint-name>/listKeys?api-version=2022-03-01"

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

# Get source code using SCM access (Azure permissions or SCM creds)
az rest --method GET \
  --url "https://<func-app-name>.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py?code=<master-key>" \
  --resource "https://management.azure.com/"

# Get source code with Azure permissions
az rest --url "https://management.azure.com/subscriptions/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/hostruntime/admin/vfs/function_app.py?relativePath=1&api-version=2022-03-01"
## Another example
az rest --url "https://management.azure.com/subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourceGroups/Resource_Group_1/providers/Microsoft.Web/sites/ConsumptionExample/hostruntime/admin/vfs/HttpExample/index.js?relativePath=1&api-version=2022-03-01"

```
{{#endtab }}

{{#tab name="Az Powershell" }}
```bash
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 }}


## Privilege Escalation

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

## References

- [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}}
