TL;DR
Token Security researchers have discovered several Azure built-in roles that are misconfigured to be over-privileged - they grant more permissions than intended by Azure.
In addition, we discovered another vulnerability in the Azure API that allows attackers to leak VPN keys.
Combined, these two issues create a new attack chain that lets a weak user gain access to both internal cloud assets and on-premises networks.
In this report, we detail the research process that led to the discoveries, their implications, and what organizations can do to stay safe against these threats and other identity-driven attacks.
What is Azure RBAC?
Before jumping in, let’s discuss some basics.
Azure’s permissions model, Azure RBAC (Role-Based Access Control), is, as the name states, based on roles.
Roles are basically groups of permissions that can be assigned to principals (users, service principals, groups, etc). When granting a role to a principal, you create a role assignment.
Every role assignment contains three main components:
- Security Principal - Who is given the permissions?
- Role Definition - Which role is assigned? What permissions does it grant? This section states the name of the role and the Actions and Not Actions (allow or deny) that this role is granting.
- Scope - What resources is the principal given access to? The scope can be vast, such as an entire management group or subscription, or more specific, like a resource group or a single resource (e.g., a specific VM or storage account).
Azure roles
In Azure, there are more than 400 built-in roles, which can be divided into 2 categories:
- Generic - Roles that grant permissions that apply across all resources and all Azure services in the given scope (e.g., Contributor, Owner, Reader, etc.)
- Service-specific - Roles that grant permissions for a specific service or function in the given scope (e.g., Storage Blob Data Reader or Virtual Machine Contributor).
If you assign the Contributor role over a subscription scope, it will grant permissions to perform actions over all of the resources in this subscription, regardless if they’re storage accounts, virtual machines, or any other resource. But if you assign the Virtual Machine Contributor, it will grant these permissions only to perform actions over virtual machines in the subscription.
So we can see the tradeoff here: using service-specific roles is the more secure approach, since you are granting fewer permissions, but the generic roles are easier to use since you need to manage fewer roles assignments.
The Problem
Let’s take a look at few roles and their permissions. See if you can tell where things go wrong.
The Reader role
One of Azure’s built-in roles, called Reader, is a generic role. As you’d expect, it gives read-only permissions over the resources in the chosen scope. Let’s examine its role definition:
As we can see in the actions
property, the permission given is */read
, which means it lets you perform any read action in the given scope.
*Note that this does not include data actions (reading data objects like files in storage accounts, key vault secrets, etc). Those require different, service-specific sensitive permissions.
Okay, so a generic role giving generic read permissions, that makes sense.
What about the permissions of service-specific roles?
Workbook Reader
If we analyze the actions
property, we can see that as the description states, it grants read permissions to a few workbook-related objects. So a service-specific role that grants access to a specific service! So far so good.
Managed Applications Reader
Now, let’s check the Managed Applications Reader role, which its description is “Lets you read resources in a managed app and request JIT access.”
We can see it has access to deployments, jitRequests, and… read everything???
So this role, which is supposed to grant access to read managed apps and JIT access, actually also allows the user to read every Azure resource?
This is clearly not what the description says, and certainly not what someone assigning the Managed Applications Reader role would expect.
Essentially, the role’s name and description are misleading the user into thinking the role grants specific permissions, when in fact it grants generic permissions to every resource.
How bad is it?
I saw this and thought to myself, okay, this is just a read permission... how bad can it be?
Well, I was seriously wrong.
Let’s dive into what we can actually do with this permission and how can it be useful for an attacker.
Since there are so many actions possible here, I divided them into three categories and gave some examples for each.
Credential theft:
- Automation Accounts, Deployment scripts, Web applications - This one really surprised me: this permission actually allows you to read source code and environment variables of scripts and applications. The common thing among the three services I listed here, is that they all interact with your environment, which makes them very likely to contain credentials and secrets!
Sensitive data discovery:
- Storage accounts, container registries, databases - Enumerating all instances and their metadata to find sensitive data spots
- Resource locks - Helps identify resources that are considered critical or sensitive
- Backup vaults - Find DB exports, storage account backups and more
Attack planning:
- Role assignments - Know about who can access what. Useful for planning privilege escalation paths
- Diagnostics settings, alerts, and log analytics workspaces - Know what is being logged, and where, and view security alerts. Useful for OpSec and detection avoidance
- Network configurations, network security groups, public IPs, virtual network gateways - Further plan attack paths and network advancements
- Key Vaults - Listing all vaults and their metadata
How wide is it?
So you might think to yourself - Okay that’s cool… but I don’t use the Managed Applications Reader role, so I am safe, right?
Well, think again.
After analyzing all Azure built-in roles, I found that this problem (having an un-needed */read
permission, basically including the Reader role) recurs in 10 different roles(!):
This risk exists in every user, service principal, managed identity, or group members that are assigned one of these seemingly innocent service-specific roles.
As we can see in the table, some of the roles have more specific read permissions in addition to the */read
, like the Microsoft.Solutions/applications/read
permission in Managed Application Operator Role, which is already included in the */read expression. This shows that one of the permissions is redundant, and may point to it being added by mistake or by laziness.
The case of App Compliance Automation Reader is even more absurd, since it only has the */read permission, and is essentially identical to the all mighty and powerful generic Reader role.
So this is pretty bad… but can we exploit it even further?
VPN Vulnerability
I wanted to make this attack scenario even stronger, and find more quirks that are possible using the */read
permission.
So I took a look its documentation:
So we understand that identities with read permissions should not be allowed to read secrets (which makes sense because those secrets can then be used to elevate privileges to more than read-only, access more resources, etc.)
Using this really nice website, I saw that there are 9,618 (!) Azure actions that are included in the */read
expression (every operation that ends with ‘/read’).
So my thought was:
If I can find one action, out of those 9,618, that will allow me to leak a secret, I will have a serious vulnerability here!
After going through many permissions in the list, I found one that piqued my interest:
What is a VPN link? What is a connection? What is a shared key? I don’t know, but it has the word ‘key’ in it, so it must be interesting :)
The bug
So we have an API call that retrieves some sort of a secret even if I only have read permissions. But why is that happening? What is the mistake that the Azure developer made here?
In Azure, API calls are implemented with different HTTP methods. For example, the Virtual Machines - List API call is implemented with GET, and Virtual Machines - Install Patches is implemented with POST. That makes sense, because the Install Patches API requires data from the user (a body in the request), while the List VMs only retrieves data from the server, and does not require any data from the user.
But what I found out through some blackbox research, documentation reading, and a lot of trial and error, is that Azure chose to enforce permissions by varying the HTTP methods used in the API requests. Let me explain:
It seems that users with read permissions alone (like */read) can issue GET requests to the API, but will get denied access if they attempt to issue POST requests.
Regular read operations, like Virtual Machine - List are implemented with a GET as we’ve seen, while operations that read sensitive values, such as Storage Accounts - List Keys or Database Accounts - List Connection Strings, are implemented with POST requests - even though the request body is empty. This is to make sure that permissions enforcement is in place, and identities with read permissions alone would not be able to access those sensitive APIs.
To prove this, I performed an API call to a URL that doesn’t exist, using a read-only identity. When I issue a GET request, I get HttpResourceNotFound
error. But when I issue a POST request to the same non-existent URL, I get AuthorizationFailed
error. This further proves that permission enforcement is determined by the HTTP method, and not by the checking specific API call and whether or not I have access to it.
So why is that problematic? Because when you choose to design your software in a way that doesn’t make sense and isn’t intuitive, your developers are bound to make some critical mistakes…
My assumption is that at some point, some Azure developer must have accidentally implemented an API call that retrieves a secret with the method that makes the most sense, which is GET, because there is no body needed in the request. And by doing so, they created a vulnerability…
If we take a look at the API call that we found earlier, we see that our theory was right! It was accidentally implemented with a GET, allowing read-only users to fetch the key!
So now we have a vulnerability. The only thing that is left is to find out what that shared key is…
Azure VPN Gateway
VPN Gateway is an Azure service that acts as a VPN, allowing customers to connect networks over the internet. Organizations mainly use this service to support hybrid environments (connecting cloud and on-premise), as well as connecting between on-premise sites (aka Site-to-Site, or S2S). Individual users can also connect to it, for scenarios such as working remotely (aka Point-to-Site, or P2S).
P2S connection types require additional authentication (certificate/radius server/Entra ID login).
But a Site-to-Site (S2S) connection type requires only the pre-shared key (PSK), which is a password that is shared between the VPN devices of each site, and the Azure VPN Gateway service. And yes, that’s the key we can fetch using our vulnerability!
The full attack
- The attacker compromises a weak identity (identity with read permissions or one that is assigned one of the many over-privileged roles we listed earlier).
- The attacker fetches the VPN Gateway pre-shared key.
- Using the key, the attacker connects to the S2S connection and accesses internal networks, including VPCs and on-premise networks that are connected to the same Azure VPN Gateway.
Proof-of-concept video:
The video demonstrates that when a principal with no privileges is assigned the Log Analytics Reader role, which is only supposed to grant access to read logs but as we know by now is over-privileged, it has permissions to fetch the VPN pre-shared key, which is then used to connect to the VPN.
With this access, attackers can create a “rogue site”, essentially granting them access to cloud resources, other sites, and secure networks of the target organization.
Depending on the configuration of the VPN Gateway, this may work only when the connection attempt is originating from the IP address configured in the Azure VPN gateway.
In that case, an attacker that has some on-premise foothold can use this trick to access sensitive cloud infrastructure and data.
There are even more sensitive values you can access using the Reader privileges. Excellent research published by Binary Security shows how you can escalate your Reader privileges in Azure API Management service by fetching subscription keys and SSO tokens, effectively resulting in a full takeover of the service.
Vendor response
Over-privileged roles
After reporting this issue to Microsoft, their response was that this is a ‘low severity’ security issue and they decided to not fix it. I later noticed some major documentation changes based on my report: All 10 over-privileged roles’ documentations were added the following sentence:
So they chose to fix the documentation instead of fixing the actual issue, which still endangers customers.
VPN PSK leak
This was acknowledged by Microsoft as an ‘Important’ severity vulnerability, and the issue was fixed. They also awarded me with a US$7,500 bounty award.
I was happy to find out that the fix was not changing the API HTTP method to POST.
In this newly created documentation page that explains Azure VPN permissions, it is stated that to fetch/update the PSK, you now need to have the Microsoft.Network/connections/sharedKey/action
permission:
This is a very good fix!
Mitigations and recommendations
Audit the use of the problematic roles
As we saw, the over-privileged roles issue is not going to be fixed. Refrain from using those roles in your environment, and use alternatives.
Use particular and limited scopes
Instead of assigning roles on a wide scope, such as an entire management group or subscription level, limit them only to the specific resource that the principal needs access to, or to a resource group if access to multiple resources is needed.
Build custom roles
Instead of using built-in roles that are not managed by you, use custom roles and grant only the needed permissions. Replace your current role assignments (at least of the problematic roles mentioned here) with custom roles that have fine-grained permissions.
Summary
Securing cloud environments and their identities is not easy.
The shared responsibility model, which is adopted and presented by all major cloud providers, clearly states which security tasks are the cloud provider’s responsibility, and which are the customer’s. But the issues we discussed here are in the gray area: when the cloud provider is giving you a service that is supposed to help you with identities and permissions management, but in fact misleads you into making dangerous decisions, who is to blame? Is it the cloud provider who caused you to create the security issue, or is it you, who actually created it?
There is no easy answer here, but one thing is clear - don’t fully and blindly trust the services that are given to you. Always double check, and be proactive and vigilant about the security of your organization.
When you have many identities and big infrastructure, this becomes a real challenge. When you have to question every role, permission, and API call, things can get complicated. But luckily, there is a solution.
To learn more about Token Security and how we can help secure your Azure environment (and many more), book a demo here.
Reach out anytime
I’m Ariel Simon, a security researcher from the Token research team, primarily focused on uncovering vulnerabilities and finding new attack techniques in cloud environments.
Feel free to contact me on LinkedIn or via email: [email protected].