After exploring the basics of Azure Active Directory (AD) and IAM in my previous blog post, we are ready to move on to my research findings. In this post I elaborate on the first part of my research on privilege escalation using managed identities.

In a future blog post, I will cover a second attack vector, abusing user-assigned managed identities to escalate from the Resource Group level to the Subscription.

What are managed identities?

A managed identity object is a service principal that’s linked to Azure resources. For example, a virtual machine (VM) might have a managed identity with a role assignment that allows it to read data from Storage Blobs. A managed identity is similar to an instance profile in AWS or a service account in Google Cloud.

There are two types of managed identities: 

  • System-assigned – a managed identity that’s directly linked to a specific Azure resource. A system-assigned managed identity can’t be assigned to another resource.
     
  • User-assigned – a managed identity that is a standalone object.
    A user-assigned managed identity is not linked to any Azure resources, and has its own lifecycle. This type of managed identity can be linked to a group of resources. 

For example, if we run an application on three VMs, and we want to manage its storage inside a storage account, we could provide a user-assigned managed identity with access to the storage account. If we would like to use a system-assigned managed identity, we must create one for each VM.

Getting a managed identity access token

The access token for a managed identity can be pulled from the MSI_ENDPOINT (usually this is the Instance Metadata Server – 169.254.169.254). Note that the resource variable might change according to the service the token is meant for. 

I used storage, in this example, because my token grants me access to a storage account.

Example: curl 'http://169.254.169.254/metadata/identity/oauth2/token?
api-version=2017-11-09&resource=https://storage.azure.com' -H Metadata:true

The Azure CLI, PowerShell, and the Azure SDK have wrappers available for this action. 

With the token that we receive, we can authenticate to the desired service.

The known privilege escalation method

The flaw here is pretty obvious. Once you are inside a VM, whether legitimately or not, you can retrieve the token. This is a very handy privilege escalation method that sometimes gives extensive permissions.

This is a relatively well-known privilege escalation method and is described by Karl Fosaaen.

Although this blog focuses on VMs, an attacker might use the managed identity of almost any service that supports this feature, including Azure Container Instances, virtual machine scale sets (VMSS), Service Fabric clusters, and more.

My research objectives

At the beginning of my research I wanted to see if there was a way for an attacker to execute in the context of managed identity permissions as a low privileged user. 

As I discovered, the answer is yes, and in this blog I will present the first of two attack vectors I investigated, which is: “Escalating from low-privileged roles to managed identities that have broader permissions.” In particular, I investigated three Azure services: Functions, Logic Apps, and Automation to find roles that allow an attacker to escalate.

Escalation to managed identities’ permissions from a low privileged user

Let’s look at it from the following perspective: what if I already have a foothold in Microsoft Azure AD and I want to escalate my privileges using a managed identity? How can we take a user with limited permissions and escalate to a more powerful managed identity? 

Azure Function research

Azure Function is an Azure serverless service. It’s similar to AWS Lambda and Google Cloud Functions. The purpose of Azure Function is to help developers eliminate the need for infrastructure maintenance and allow them to focus primarily on coding. Azure Function is a container-based service, and the user can choose the runtime. 

A collection of functions is contained within a FunctionApp, which is also the object that manages the identity of the functions. To allow a function using a managed identity, the creator of the function has to assign the identity to the FunctionApp.

I looked for a role that would allow me to create a new function in an existing FunctionApp. For my purposes, the role needs “Microsoft.Web/sites/functions/*” permissions, and I found a role named “Website Contributor” to abuse.

This role, which seems pretty harmless, can create a new function inside of a FunctionApp and run code in the context of the FunctionApp managed identity.

For my demonstration, I created a user, named him WebTest, and assigned him the Website Contributor role in the scope of a Resource Group named Functions-RG. In this Resource Group I created a FunctionApp named “orca-poc-func”. I chose PowerShell runtime and attached to it a system-assigned managed identity with Contributor role on the Subscription. 

My goal is to read data from another Resource Group I named Orca-Security. In this Resource Group, I have created a storage account named “orcapesa.”

First, let’s login with WebTest user and see if we can list storage accounts in another Resource Groups. 

In the image below, we can see zero storage accounts as WebTest.

After verifying that our user is low-privileged, it is time to escalate. I created a Function named HttpTriger1.

In the function, we list the Resource Groups in the subscription using Get-AzResourceGroup.

Great! So now we are aware of the existence of a new resource group named Orca-Security, which is outside of our scope of permissions. Let’s find out which storage accounts it contains using Get-AzStorageAccount.

And finally, let’s pull the keys from our storage account. Those keys can be used to perform any operation in the storage account including reading data from Blobs. We can use Get-AzStorageAccountKey.

And that’s it. Our low-privileged user, which doesn’t have access to a storage account in other Resource Groups, has succeeded in pulling the storage account keys of an account from another resource group in the subscription. This was possible by leveraging the managed identity of the FunctionApp. 

I would think twice before assigning a Website Contributor role next time.

Logic App Research

Logic App is an Azure service used to create and integrate automated workflows. 

With its user-friendly interface, one can create an automated integration between applications and services. A workflow consists of a trigger and an action. There are two types of Logic Apps: Standard and Consumption.
Consumption is priced per use, and Standard is priced based on hosting plan. 

To assign a managed identity to a workflow, you must link it to the Logic App itself. I looked for a role that allows me to edit an existing workflow or create a new one in order to abuse its privileges. I found the role “Logic App Contributor” that contains the permission “Microsoft.Logic/*”.

This role allows a user to perform any operation on a Logic App, a common need when building an integration solution. But what if the Logic App is using a managed identity? In this case, an attacker who possesses a user with “Logic App Contributor” role could elevate his privileges to the managed identity’s permissions, which might have broader permissions. 

For the demonstration I created a user and named him Albert. I created a Role Assignment to grant Albert Logic App Contributor role in the Orca-Security Resource Group. I also created a Logic App named “test-App” with a managed identity that has a contributor role in the subscription. 

My goal is to prove that by leveraging the system-assigned managed identity permissions, Albert could escalate its privileges and query a storage account I created in another Resource
Group called Functions-RG.

First, let’s login as Albert and see if we can list any storage accounts in another Resource Group in our Subscription. In the image below, we can see there are no accounts.

We have verified that Albert doesn’t have enough privileges, so let’s escalate.

We have a Logic App named “test-App” that uses managed identity, and we can edit its workflow.

I chose the HTTP action and triggered a GET request to the endpoint. 

To leverage the managed identity, I added the authentication header.

And that’s it. The body of the response contains the list of the storage containers in “pocprivatesa” storage account, which is in another Resource Group. Albert has contributor privileges in the Subscription.

Automation Service Research

As the name suggests, Automation is a service for automating frequent and time consuming tasks. To start working with Azure Automation, we first need to create an Automation Account. This Account is an environment that can consist of multiple automation objects such as Runbooks and Webhooks. An Automation Runbook is a compilation of routine procedures that a user wants to automate. It can be written in several different programming languages such as Python and PowerShell.

When creating an Automation Account, a RunAs account is automatically created. The RunAs account provides authentication to manage resources deployed on the classic deployment model and assigned by default to a Subscription Contributor. Although the user might be low privileged, their Runbook will execute with the permission of the RunAs user. 

I found a role named Log Analytics Contributor with extensive privileges on AutomationAccount service and escalated privileges with it. Thanks to its full permissions over Automation service, I could execute Runbooks in the context of a subscription contributor. During the course of my research, Microsoft deleted its Automation privileges and prevented the possibility to escalate privileges by this role. This finding was reported to MSRC, acknowledged as a vulnerability and was fixed.

That concludes the first half of my research on privilege escalation using managed identities. Stay tuned for my next post where I wrap up my research with a look at abusing user-assigned managed identities to escalate from the Resource Group level to the Subscription.