10 ways of gaining control over Azure function Apps

Billuk21
XM Cyber
Published in
18 min readFeb 14, 2022

Written by Bill Ben Haim & Zur Ulianitzky — February 14,2022

Overview

Pen-testers! Red-teamers! We’ve prepared a bucket of new Azure techniques, specifically about Azure function app sites. In this blog, we’ll show you new approaches that attackers can actually use to compromise your critical Azure assets. Our attacker has gained access to an identity, user, or group, and has sufficient Resource manager RBAC permissions to perform attacks. We will demonstrate the attacker’s every move, step by step.

Note: If you’re not familiar with Azure Active Directory and the Resource Manager permissions model, follow our series of blogs about it - Part 1 & Part 2.

Azure Functions recap

First, let’s do a quick recap about Azure function app sites.

Security architecture

Function app sites are actually serverless websites. They work even without any functions installed.

When you install a function app site, you have to define the runtime stacks (programming language) that will run on that function app site. It could be Java, Python, Node.js, PowershellCore, .NET, and a custom handler.

If you want the function app site to respond to events, you need to install the functions, which are the actual code blocks that run behind the scenes. These are not standalone, but rather connected to the function app site.

These functions offer the following levels of authorization:

  • Anonymous: No key is required.
  • Function: A function-specific key or a host key is required.
  • Admin: The Master key is required.

Function access keys are the simplest way to protect an Azure Function from unauthorized access. When accessing a function, you can provide the access key in the code query parameter or the x-functions-key HTTP header. There are two access scopes for function-level keys:

  1. Function-specific: These keys apply only to the specific functions under which they are defined.
  2. Host: These keys can be used to access all functions within the Functions app.

The Master key also provides access to all functions within the Functions app, as well as administrative access to the runtime REST APIs.

SCM management panel

By default, every Azure app service (Functions, App Service, and Logic Apps) deploys the SCM management panel, giving web administrators access to make configurations on the app itself. The SCM is based on an open-source, .NET project called KUDU.

What we cover in this blog

Attacker approaches

Here’s what the attacker will want to do:

  • Reach the SCM from Azure Resource Manager
    For this, we’ll look into which credentials are supported by the SCM.
  • Compromise the Master keys
    We’ll look into the required APIs needed for this, as well as other ways to find those access keys that are dependent on permissions.
  • Modify the function source code
  • Abuse an authentication mechanism in order to compromise the Functions app
  • Control network access
  • Enable disabled features and abuse them

Method contents

When we describe the attack methods, we’ll cover the following:

  • Attack description — What can be achieved when utilizing this attack method
  • Conditions — Requirements to reach the attacker’s objective, such as network access, disabled features, access to specific credentials, and more
  • Relevant RBAC permissions — The exact permissions required to execute the operations within the specific APIs for each attack
  • Proof of concept — A demonstration of each attack method, while elaborating on each step the attacker takes along the way.

Table of Contents

Attack methods:

Don’t be a disabler — nobody likes it:

Method 1: Execute commands and overwrite

Attack description

From the attacker’s perspective, obtaining the function’s Master key provides administrative access to the function’s code. An Azure function’s source code can disclose sensitive information (keys, secrets, passwords). In addition, it enables the attacker to overwrite the function’s code to malicious code and execute commands on the container.

Attack conditions

  • SCM enabled: No
  • SCM credentials: No
  • FTP enabled: No
  • Functions enabled: Yes
  • Requires API keys: Yes, Master keys
  • Requires network access: Yes, only the function app site

Relevant RBAC permissions

  • Microsoft.web/sites/host/listkeys/action
  • Microsoft.web/sites/functions/read
  • Microsoft.web/sites/read

Proof of concept

Here’s what the attacker does:

  1. Enumerates all the function app sites within the subscription:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>}/providers/Microsoft.Web/sites?api-version=2019–08–01

2. Lists all the functions within the function app site, focusing on two parameters:

Invoke_url_template and script_href

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/providers/Microsoft.Web/sites/<FUNCTION NAME>/functions?api-version=2019–08–01

3. Lists the Master key of that function app site:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/providers/Microsoft.Web/sites/<FUNCTION NAME>/host/default/listkeys?api-version=2019–08–01

AZ CLI command:

4. Accesses the source code of a targeted function within the function app site by using the information gathered from the previous steps:

URL: https://<FUNCTION APP ID>.azurewebsites.net/admin/vfs/site/wwwroot/<FUNCTION NAME>/index.js

The server’s response:

So great, now the attacker can see the source code of the function HttpTrigger, which in this case does not include any sensitive information that can be leveraged nor any other potential vulnerability that might be exploitable by the attacker.

5. Overwrites the function’s code with malicious code. The attacker can use an HTTP PUT request to modify the function’s code on the server-side, and wrap the name parameter with the eval function.

URL: https://<FUNCTION APP ID>.azurewebsites.net/admin/vfs/site/wwwroot/<FUNCTION NAME>/index.js

6. Checks if the source code was successfully changed by triggering the same HTTP request as in step 4.

The server responds with the following:

7. The attacker’s objective is to find a way to gain control over the server or access sensitive information that can be leveraged for privilege escalation or lateral movement.

Since the attacker embedded the eval function, they can use the process global object that provides information about the current Node.js process.
This is a concatenation of 3 different methods:

  • Process.platform: The type of OS architecture
  • Process.title: Holds information about the current execution of the Node.js process
  • Process.cwd(): Provides better understand of the current folder location

Note: The payload within the name parameter is URL encoded.

The server response:

8. Executes commands on the container, by using two Node.js modules:

  • FS (filesystem) — for example, the following methods: fs.ReadFileSync(), fs.Readdirsync(), fs.Writefilesync()
  • Child Process — for example, the following methods: exec(), Spawn()

When utilizing the functions of the modules mentioned above to execute OS commands, the output of the commands’ execution will not be sent back to us, and therefore, it’s possible to append the commands’ execution output to a file on disk, then read the file.

For example, its possible to run a python script that will execute and save to a local file all the environment variables of the container, then its possible to read the file which contains the output of the previously executed commands.

This was already introduced in our lecture in HITB Abu Dhabi 2021. In the lecture, we demonstrated advanced attack scenarios on Azure Active Directory. You can watch at the following link:

Attack scenarios Abusing Azure Active Directory

Method 2: Overwrite function’s code from the Azure portal

Attack description

The attacker can use the hostruntime permission to overwrite the function’s code from the Azure management API.

Attack conditions

  • SCM enabled: No
  • SCM credentials: No
  • FTP enabled: No
  • Functions enabled: Yes
  • Requires API keys: Depends on the configuration of the function’s auth level.
  • Requires network access: Yes

Relevant RBAC permissions

  • Microsoft.Web/sites/hostruntime/*

Proof of concept

Here’s what the attacker does:

After collecting all the permissions for the function app site and for its functions, as we showed in the first example, the attacker uses the following request to modify the function code:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/resourceGroups/<RESOURCE GROUP NAME>/providers/Microsoft.Web/sites/<SITE NAME>/hostruntime/admin/vfs//<FUNCTION NAME>/<FILENAME>?relativePath=1&api-version=2018–11–01

Where <FILENAME> includes the extension

The server response:

Let’s make sure the code was changed:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/resourceGroups/<RESOURCE GROUP NAME>/providers/Microsoft.Web/sites/XMfunctionapp/hostruntime/admin/vfs/HttpTrigger1/index.js?relativePath=1&api-version=2018–11–01

This means we can abuse it again and run commands on the container.

Method 3: Basic Authentication SCM service attack

Attack description

By default, upon creation, the SCM site supports Basic Authentication. You can see this in the KUDU service documentation.

https://github.com/projectkudu/kudu/wiki/Accessing-the-kudu-service#authentication--authorization

Conditions

  • SCM enabled: Yes
  • SCM credentials: Yes
  • FTP enabled: No
  • Functions enabled: No
  • Requires API keys: No
  • Requires network access: Yes, the SCM site needs to be accessible.

Relevant RBAC permissions

  • Microsoft.Web/sites/config/list/action
  • Microsoft.Web/sites/Read

Proof of concept

The SCM site is created and supports Basic Authentication on the following URL:

https://<SITE NAME>.scm.azurewebsites.net/BasicAuth

Great, so where does the attacker get the credentials from?

  1. Uses the following request to reach the credentials:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/resourceGroups/functionappRG/providers/Microsoft.Web/sites/functionappxm/config/publishingcredentials/list?api-version=2018–11–01

2. Then enters the credentials to the Basic Authentication login window:

Successfully logged in to the SCM site:

3. From here, the attacker can execute commands, read sensitive data, steal attached identity tokens, install extensions, and more.

Method 4: Publish XML SCM service attack

Attack description

Aside from the third method, there is another way to expose the credentials to the SCM and attack it.

Conditions

  • SCM enabled: Yes
  • FTP enabled: No
  • Functions enabled: No
  • Requires API keys: No
  • Requires network access: Yes, the SCM

Relevant RBAC permissions

  • Microsoft.Web/sites/publishxml/Action

Proof of concept

Here’s what the attacker does:

Sends an HTTP POST request to the following URL:

URL: https://management.azure.com/subscriptions/3bb<SUBSCRIPTION ID>/resourceGroups/functionappRG/providers/Microsoft.Web/sites/XMfunctionapp/publishxml?api-version=2018–11–01

As you can see:

  • By default, Azure creates the same password for the three services: Web Deploy, FTP, and Zip deploy.
  • In addition, the user name that is being used for those services is the same as the name of the actual function app. In our case, it’s $XMfunctionapp (doesn’t matter whether you put the $ sign at the beginning of the name).

Method 5: Use management permissions to generate JWT token for SCM

Attack description

As we discussed in the above scenarios, you can log in to the SCM’s dashboard by using a JWT token that is being generated on the Azure management.

Conditions

  • SCM enabled: Yes
  • FTP enabled: No
  • Functions enabled: No
  • Requires API keys: No
  • Requires network access: Yes, the SCM

Relevant RBAC permissions

  • Microsoft.Web/sites/publish/Action

Proof of concept

We checked command from the Kudu Service API on GitHub:

So here’s what the attacker does:

  1. When connecting with a user, runs a get token command:

az account get-access-token \-\-resource-type arm

2. Uses the extracted token to run the command:

URL: https://xmfunctionapp.scm.azurewebsites.net/api/command

The server response:

Method 6: Add a publishing user to SCM

Attack description

In this method, the attacker creates a new user that is valid for both the SCM and the FTPS services. Especially when the SCM is disabled or not accessible from the network, you can use the FTP to modify the function code and execute commands over the container.

Conditions

  • SCM enabled: Yes
  • FTP enabled: No
  • Functions enabled: No
  • Requires API keys: No
  • Requires network access: Yes, the SCM or the FTP

Relevant RBAC permissions

  • microsoft.web/publishingusers/write
  • Microsoft.Web/sites/basicPublishingCredentialsPolicies/Write
  • Microsoft.Web/sites/publish/Action

Proof of concept

Here’s what the attacker does:

  1. Sends the following HTTP request:

The server response:

2. Uses it on the SCM service by using the following details:

Username: XMfunctionapp\Testingxmuser12345678912

3. The attacker connects to the Function app using the basic authentication and the new credentials:

4. Successfully connect to the SCM panel:

Method 7: Modify function code by using the FTP service

Attack description

Just starting a function app it installs, among other features, the FTP service which give you the options to both access the function app functions and even modify and change the current files.

Conditions

  • SCM enabled: No
  • FTP enabled: Yes
  • Functions enabled: Yes
  • Requires API keys: Depends on the auth level of the function app
  • Requires network access: Yes, function app

Relevant RBAC permissions

  • Microsoft.Web/sites/basicPublishingCredentialsPolicies/ftp/Read

One of the following attack methods:

  • Third Method — Attacking function app SCM service
  • Fourth Method — extract SCM credentials by using the public XML API
  • The sixth attack method — add publishing user to SCM
  • Any other way that you found that can be exploited

Proof of concept

Here’s what the attacker does:

  1. Enumerates the FTP service to discover whether it is enabled or disabled:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/resourceGroups/functionappRG/providers/Microsoft.Web/sites/XMfunctionapp//basicPublishingCredentialsPolicies?api-version=2021–02–01

The server response:

2. As we can see, the FTP is currently enabled, meaning the attacker just needs credentials for it. Hence the following:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/resourceGroups/functionappRG/providers/Microsoft.Web/sites/XMfunctionapp/publishxml?api-version=2018–11–01

3. Great, so the attacker authenticates to the FTP service using the just-obtained credentials:

The FTP service address: waws-prod-dm1–205.ftp.azurewebsites.windows.net

4. The attacker downloads the index.js file and discovers that the it doesn’t anything exploitable in it:

5. The attacker has the permissions to change the current index.js file to a malicious index.js file:

6. Moving it to the the HttpTrigger function folder:

7. And can now access the malicious code from the function app:

https://xmfunctionapp.azurewebsites.net/api/HttpTrigger1?name=require('fs').readdirSync('/home/site/wwwroot/HttpTrigger1/').toString()

8. The server response, with the content of the folder HttpTrigger in the remote server:

Method 8: Modify function code

Attack description

When creating the function app site using the Zip Deploy feature, it exposes a different API. When accessing the function’s code upload feature, instead of giving access to the /vfs/admin/ URI, it exposes only the /api/vfs/ URI. The APIs will be related to the site extensions permissions, and not HostRuntime as we saw in the second scenario.

Conditions

  • SCM enabled: No
  • FTP enabled: No
  • Functions enabled: Yes
  • Requires API keys: Depends on the functions auth level
  • Network access? for each feature: Yes

Relevant RBAC permissions

  • microsoft.web/sites/functions/read
  • microsoft.web/sites/read
  • microsoft.web/sites/extensions/

Proof of concept

The attacker sends the following HTTP PUT request:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/resourceGroups/TestResources/providers/Microsoft.Web/mysite/extensions/api/vfs/site/wwwroot/HttpTriggerJS1/index.js?api-version=2018–11–01

The server response:

Method 9: Change source control (GitHub)

Attack description

Azure function app deployment center allows users to use different source controls for their CI/CD process. Attackers with the right permissions can abuse those features and gain control of the server.

Conditions

  • SCM enabled: No
  • FTP enabled: No
  • Functions enabled: Yes
  • Requires API keys: Depends on the functions auth level
  • Requires network access: Yes

Relevant RBAC permissions

  • Microsoft.Web/sites/sourcecontrols/Read
  • Microsoft.Web/sites/sourcecontrols/Write
  • Microsoft.Web/sites/sourcecontrols/Delete
  • Microsoft.Web/sites/Write
  • Microsoft.web/sites/read
  • Microsoft.web/sites/functions/read
  • Microsoft.Web/sites/config/read
  • Microsoft.Web/sites/config/list/action

Proof of concept

The attacker uses the source control option inside the deployment center of the Azure function app or app service. The same permissions work for both services. This way, the attacker performs the CI/CD process from different platforms, such as Github Actions, Local Git, Bitbucket, and more.

Assuming the attacker has the correct permissions, they can change the current source control provider and add a malicious one. This way, they get control of the application.

The attacker does the following:

  1. Executes the following HTTP PUT request to modify or create a different provider for the source control of that app service of function app:

URL: https://management.azure.com/subscriptions/<subscription id>/resourceGroups/<Resource group name>/providers/Microsoft.web/sites/phpappbbh/sourcecontrols/web?api-version=2020–12–01

The server response:

2. So it means that now GitHub Actions will deploy the code from the main branch of the PHP repository. Let’s see what that code contains:

As we can see, there is a simple one-liner web shell. It enables the attacker to execute commands on the server.

3. The attacker browses to the Azure app service and executes the following command:

https://phpappbbh.azurewebsites.net/?cmd=id%26ls -la /home
And receives the following server response, showing the results of the commands executed successfully:

Method 10: Read Admin token and get Master key

Attack description

With this method, the attacker can exploit the function app even if the SCM is not accessible from the network (like we saw in the fifth and sixth methods). All the attacker needs is the Master key, and they can modify the function code or access sensitive information.

Conditions

  • SCM enabled: No
  • SCM credentials: No
  • FTP enabled: No
  • Functions need to be enabled: Yes
  • Need API keys? if yes, which one: No
  • Network access? for each feature: Yes, the function app site.

Relevant RBAC permissions

  • Microsoft.Web/sites/publish/Action
  • Microsoft.web/sites/functions/token/read

Proof of concept

Here’s what the attacker with the right permissions does:

  1. Sends the following request:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/resourceGroups/functionappRG/providers/Microsoft.Web/sites/XMfunctionapp/functions/admin/token?api-version=2021–02–01

The response from the server:

Uses the obtained token in the following request to extract the master keys (with the admin token in the Authorization header):

URL: https://xmfunctionapp.azurewebsites.net/admin/host/systemkeys/_master

The server response:

Don’t be a disabler — nobody likes it

All the attacks are quite cool, right? But in our experience, it’s never that easy and most of the time something won’t work right. Attackers also need to be able at least to understand, and sometimes even modify, the disabled/inaccessible/misconfigured feature that they want to attack.

We will demonstrate a couple of techniques on how to handle and even modify (depending on your current permissions) the disabled feature.

Method: Enabling SCM

Attack description

We saw that it’s possible to attack the SCM from both the Web deployment and the actual FTP service. The attacker will also want to make sure that one of them is enabled.

Relevant RBAC permissions

  • Microsoft.Web/sites/Read
  • Microsoft.web/sites/functions/read
  • Microsoft.Web/sites/basicPublishingCredentialsPolicies/Read
  • Microsoft.Web/sites/basicPublishingCredentialsPolicies/Write
  • Microsoft.Web/sites/config/list/action

Proof of concept

First, as attackers, we can check the current status of each service by using the following:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/resourceGroups/<RESOURCE GROUP NAME>/providers/Microsoft.Web/sites/<SITE NAME>/basicPublishingCredentialsPolicies/ftp?api-version=2021-02-01

Depending on the result, If enabled then the attacker can continue to one of the techniques described in the article. If not, the attacker can use the following to re-enable it:

Note: In this case, if the server response is 200 OK and the allow parameter value is false, then the FTP service will be disabled. If true, then it will be enabled.

Method: Enabling the function app functions

Attack description

The attacker is able to obtain the master keys or modify the function’s code, but if it’s not enabled then they can’t really do anything with it. Hence this is how they enable it.

Required RBAC permissions

  • Microsoft.Web/sites/functions/*
  • Microsoft.Web/sites/host/*

Proof of concept

First of all, the attacker wants to understand whether the function to attack is disabled or not. To do so, the attacker will need to send an HTTP GET request to the following:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/resourceGroups/<RESOURCE GROUP NAME>/providers/Microsoft.Web/sites/<SITE NAME>/functions/HttpTrigger/properties/state?api-version=2018–11–01

The server response:

So as we can see, the function HttpTrigger1 is currently disabled. Let’s see how to change the status of the function, in order to exploit that.

Although it seems that an HTTP POST request was sent, the actually sent request is an HTTP PUT request to enable the function.

The server response:

The function was successfully enabled, and now the attacker can attack!

Method: Changing the function’s authorization level

Attack description

As we wrote in the beginning of the article, the function app’s functions can have different authorization levels, a configuration used to prevent unauthorized access. It means that even though the attacker can modify the function’s code, if they don’t have the correct APIs, then the whole attack is kind of useless. It won’t help the attacker execute commands on the remote container.

So what to change?

Within every function directory in the server there’s an function.json file that configures the type of authorization levels enforced on the function. Let’s see an example:

The authLevel parameter’s value is admin, which means that only HTTP requests with the Master key can interact with this API. So basically if the attacker can change the function.json configuration in any way, it gives them the options to access our function even anonymously.

Relevant RBAC permissions

  • microsoft.Web/sites/hostruntime/* — If the /vfs/admin/ URI is exposed
  • microsoft.web/sites/extensions/* — If the /api/vfs/ URL is exposed
  • FTP attack — If the FTP is accessible and you have the credentials for them (for example, as in methods 3, 4, 6, and 7)

Proof of concept

Right now we have the function app function called HttpTrigger3, and as we saw earlier in the function.json file, the function needs the admin authorization level (Master keys). The attacker doesn’t have this, and it prevents them from interacting with the function.

Look:

URL: https://xmfunctionapp.azurewebsite.net/api/HttpTrigger3

To which the server response is:

So here’s what the attacker does:

  1. Procures the correct permissions to change the authLevel to anonymous:

2. And now the attacker accesses the same function again:

URL: https://xmfunctionapp.azurewebsite.net/api/HttpTrigger3

3. And now they can interact with the API, thus the server’s response:

Method: Modifying firewall rules for network access to the function app or SCM

Attack description

So as we previously discussed in Attacker approaches, network access to the SCM or function app itself can be a huge issue. The attacker could have all the correct permissions to change the code of the function, or could have obtained the credentials to the SCM. But the network firewall rules could still prevent them from accessing that service.

You can enumerate the current firewall rules with the following HTTP request:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/resourceGroups/<RESOURCE GROUP NAME>/providers/Microsoft.Web/sites/<SITE NAME>/config/web?api-version=2018-11-01&_=1642279972890

The server response:

As you can see, the functionapp firewall rules have the parameter action:”Deny” for any IP address (private or public).

An attempt to access the function app site yields the following response from the server:

Relevant RBAC permissions

  • Microsoft.Web/sites/config/Write
  • Microsoft.Web/sites/config/Read

Proof of concept

Here’s what the attacker does:

  1. Uses the following HTTP PUT request to modify the ipSecurityRestriction:

URL: https://management.azure.com/subscriptions/<SUBSCRIPTION ID>/resourceGroups/<RESOURCE GROUP NAME>/providers/Microsoft.Web/sites/<SITE NAME>/config/web?api-version=2018–11–01

The server response approves the request and changes the status of the ipsecurity restrictions:

2. Now the attacker tries to access the function app site again and request is successful:

Best practices

Let’s go over some best practices that you should hold to, so as to prevent attacks such as those we describe above:

  • Remove unnecessary permissions from users, groups, and identities.
  • Limit the number of principals that have permission to perform sensitive actions within your environment.
  • Limit the scope of permissions, be as minimalist as you can.
  • Use privileged identity management to control role assignments within Azure resources.
  • Don’t use hard-coded credentials within your code.

References

Before starting our research we used the following references:

--

--