Microsoft’s Azure is a complicated system of principals, securable objects, and the various ways access is granted to those objects. Some privileged actions are tightly controlled by Azure AD roles, while other actions are controlled by roles and object ownership. Many objects in Azure are subject to distinct permissions systems, which can make effective access auditing very difficult.
In this post, I will explain how one of those permissions systems can be abused to escalate to Global Admin. I’ll explain how you as an attacker can abuse this system, and I will also explain how you as a defender can find, clean up, and prevent these abusable configurations.
This is not wholly original work. A few folks in the Azure world have spoken about this risk and possible mitigations already:
- Most notably, Sahil Malik discussed the risks of particular API permissions here and proposed his own mitigations here.
In the Azure offensive security world, at least two people have discussed API permissions abuse:
In the Azure defensive security world, Doug Bienstock, Juraj Sucik, and Jacob Skiba have created a tool called Mandiant Azure AD Investigator to help find evidence of adversaries abusing Azure app roles.
I also found Marius Solbakken’s blog to be an absolute treasure trove of Azure information which really helped me understand some of the more nuanced details of Azure.
Azure AD uses the concept of “roles” to dole out privileges to principals. For example, “Global Admin” is an Azure AD directory role. Azure API permissions are a wholly distinct, parallel set of permissions that can be granted to Azure service principals. There is some overlap between Azure AD directory roles and Azure API permissions, but I think it’s best to think of them as parallel privilege systems.
These parallel systems can be used to control access to the same objects. They can be used to grant access to the same objects. But the particular privileges granted within the Azure API permissions system are only taken into account when a principal is operating against the target object through that API:
Before we go any further, let’s establish some vocabulary here. These systems are very complex and it’s easy to get confused when learning about this.
Principal — an identity that can be authenticated. In Azure-land, a principal can be a user or service principal. When logging in with a username and password, you are authenticating to Azure with your user principal.
Azure AD App Registration — an application object residing in an Azure tenant. Azure Apps are where all the nifty configurations happen, where you grant users access to the app and have the app do “things”.
Service Principal — the identity an Azure App uses when it needs to authenticate to Azure. Service Principals can authenticate with a username and password — just like a user can. And just like a user, Service Principals can have control of other objects in Azure.
API Permission — an atomic, uniquely identifiable privilege, scoped to a particular Azure App. API Permissions come in two flavors: “Delegated” and “Application”. API Permissions describe what particular privilege is granted to the Azure App.
API Permissions in the MS Graph API are written in “Resource.Operation.Constraint” format. Example: “Directory.ReadWrite.All” means that the principal granted this permission can Read and Write to All objects in the Directory.
App Role — a permission granted by the Azure App, directly usable by the principal it has been granted to.
Delegated Permissions — a permission granted by the Azure App, but only usable on behalf of a user that has authenticated to the app. Principals cannot use Delegated roles themselves, but they can impersonate a logged on user who *does* have that role, using the role on the user’s behalf.
Application App Role — a permission held by the Azure App itself. The app can use this role without a user needing to log into the app first.
Resource App — the uniquely identified service principal associated with the application that your Azure App accesses. App Roles are defined per Resource App.
Depending on the context, all of these terms can refer to the same object: Service Principal, Enterprise Application, Resource App, and First Party Application.
Confused yet? I sure as hell was. Let’s get visual and explain these moving parts and how they connect to form attack paths.
One of the most common Resource Apps you will interact with as an Azure admin, defender, or attacker is Microsoft Graph. Basically, all of the abusable administrative actions you’d ever want to take are possible through the Microsoft Graph API.
Every Azure tenant has a Microsoft Graph Resource App. You can find it in your own tenant by searching for its (current) display name, “GraphAggregatorService”. In my tenant (and yours, and every other tenant), the “Application ID” for Microsoft Graph is 00000003–0000–0000-c000–000000000000:
Why the same ID? Because this app actually lives in a Microsoft-controlled Azure AD tenant. Let’s start thinking about these things in terms of a graph:
The app resides in the Microsoft tenant, but is instantiated as a service principal (or “resource app”) in the SpecterDev tenant. These objects have the same display name, but they are different objects with different IDs. Additionally, the trust boundary represented in blue above means a Global Admin in the Microsoft tenant can’t control the Resource App in the SpecterDev tenant, and that a Global Admin in the SpecterDev tenant can’t control the Azure App in the Microsoft tenant.
Now let’s add some app roles into the mix. App roles are specific to each resource app. For the sake of explaining one of the privilege escalation possibilities here, we are going to focus on two app roles: AppRoleAssignment.ReadWrite.All and RoleManagement.ReadWrite.Directory:
At this point, these app roles are merely available for an admin to grant to a service principal, but no one actually has these permissions yet. Let’s go ahead and grant the “AppRoleAssignment.ReadWrite.All” app role to another service principal. We are going to make this an “Application App Role” (as opposed to a “Delegated Permission”), so that the service principal itself has this privilege:
And let’s not forget that our “MyCoolAzureApp” service principal is associated with the Azure App, “MyCoolAzureApp”:
The stage is now set for “MyCoolAzureApp” to turn itself or anyone else into a Global Admin. To understand this, let’s discuss what these two particular app roles allow our service principal to do:
The Microsoft documentation says that the “AppRoleAssignment.ReadWrite.All” permission:
“Allows the app to manage permission grants for application permissions to any API (including Microsoft Graph) and application assignments for any app, without a signed-in user.”
What does this mean? This means that “AppRoleAssignment.ReadWrite.All” lets you grant yourself whatever API permission you want. This particular role also bypasses the manual, human-involved admin consent process. Having this role means that “MyCoolAzureApp” can grant itself “RoleManagement.ReadWrite.Directory”:
And what can we do with “RoleManagement.ReadWrite.Directory”? The documentation says:
“Allows the app to read and manage the role-based access control (RBAC) settings for your company’s directory, without a signed-in user. This includes instantiating directory roles and managing directory role membership, and reading directory role templates, directory roles and memberships.”
In other words, you can grant yourself any directory role you want, including Global Admin:
And with that, our attack path has emerged:
- The MyCoolAzureApp app runs as the MyCoolAzureApp service principal.
- The MyCoolAzureApp service principal has the “AppRoleAssignment.ReadWrite.All” privilege, allowing itself to grant itself “RoleManagement.ReadWrite.Directory”.
- After granting itself “RoleManagement.ReadWrite.Directory”, the MyCoolAzureApp service principal can promote itself to Global Admin.
Here is a video of this attack path in action:
And here is the example attack code from the demo above:
In our last post about Azure privilege escalation, we provided some commentary around what Microsoft themselves may be able to do to mitigate or eliminate that particular attack path. An existing mechanism protects Global Admins from having their passwords reset by someone without the Global Admin or Privileged Authentication Admin role. We see that mechanism as potentially shutting down the entire attack primitive discussed in that post.
But this situation is different. It’s not immediately clear what Microsoft would be able to do to prevent privilege escalation via Azure App role abuse. These app roles are used by various organizations to support automated processes, and so the fact that the AppRoleAssignment.ReadWrite.All role allows you to bypass the admin consent experience really can’t be seen as a flaw, but as a crucial feature that enables full automation of privileged actions.
So what can we as defenders do about this? First, it’s prudent to proactively audit which, if any, of your apps have one of these two very dangerous app roles. As with everything in Azure, there are several ways to do this, but my favorite method is definitely using PowerShell.
To use this script, you will need a valid user and password for the Azure AD tenant. This user does not need any special roles or permissions — every authenticated principal can read this information. Populate the $AzureTenantID variable with your tenant ID, the $AccountName variable with your username, and the $Password variable with your plain-text password. Then, you can copy and paste the entire script into a PowerShell console, or dot invoke it:
Here’s the script in action:
This is your first prevention opportunity. The output of that script is telling us that two apps, “ThisAppHasOAuth2Permissions” and “MyCoolAzureApp” have app roles allowing those apps to promote themselves or another principal to Global Admin. Carefully determine whether those apps actually require those app role assignments, whether they can be changed to delegated permissions, or removed in favor of less powerful app roles.
If these app role assignments must remain, then recall from our last post that certain roles allow other users to create new credentials for service principals. The tenant-level and app-level “Application administrator,” “Cloud application administrator,” and “Hybrid identity administrator” roles directly or indirectly allow for this. Additionally, owners of application objects and their associated service principals can add new credentials for the app. Finally, any service principal with the Application.ReadWrite.All app role can add new credentials to any other service principal.
Don’t forget that Privileged Identity Management (PIM) means you need to audit principals with roles currently active, and users who can grant themselves a role.
Here’s what all those different possibilities look like in a graph:
Seems intimidating, right? Don’t forget that security groups can have roles now, so the above illustration can actually be rather simple in comparison to what’s actually possible.
This is your second (and third, fourth, fifth, etc.) prevention opportunity. Carefully audit the principals that can control highly privileged service principals and remove role assignments, ownership, and app roles where possible.
I very strongly recommend investing your efforts into removing the most dangerous configurations you can whenever possible. If an adversary abuses these configurations to escalate to Global Administrator in your tenant, it can be very expensive to completely remove the various persistence mechanisms available to the adversary with this level of privilege. Even then, new research is coming out all the time about novel persistence tradecraft in Azure, so you may not even be able to guarantee you’ve identified and removed all persistence installed by the adversary.
Even so, you may find yourself in a situation where these dangerous configurations must remain. There is also value in detecting when administrators legitimately add these dangerous configurations to your environment. There are a couple of options for detection particular to dangerous API permission grants.
First, you have the built-in “Audit-Logs” feature in Azure, which will by default log every API permission grant, telling you who made the change, who the permission was granted to, and what the permission and its scope are:
Next, you have the option of building a log analytics workspace and viewing all relevant events in a roll-up view in a monitoring workbook. Here’s how you set this up:
First, you need a Log Analytics workspace. You can either use an existing workspace or create a new one. Then, in our Azure tenant view, select “Workbooks” under “Monitoring” to scope your Workbooks to your Log Analytics Workspace:
Then, under the “Troubleshoot” section, you can see an example workbook which includes new app role assignments. This workbook is called “Sensitive Operations Report”:
Click this workbook, then select “New permissions granted to service principals”, which will show you whenever a service principal has been granted an app role:
Azure is a complicated and dynamic system, with new attack paths appearing, disappearing, and reappearing over time. There are very well-designed mechanisms in Azure that prevent the emergence of some attack paths, but those mechanisms are not guaranteed to exist or go unchanged in the future. Microsoft has some insanely clever people working for them, but at the end of the day, we must remember two things:
- Microsoft has made it clear that you are responsible for the configurations you or anyone else makes in your environment.
- Security serves the business, not the other way around. Perfect security is unachievable, but we can and must make proactive moves instead of relying on reactive moves. An hour of prevention is worth weeks of incident response.
Thank you Stephen Hinck and Matt Hand for reviewing this post.