Unwelcome Guest: Abusing Azure Guest Access to Dump Users, Groups, and more
Abusing Guest Access: Dumping User Lists and Group Membership with Guest Access in Azure AD
This post will walk through a user, group, and application enumeration attack against a tenant where the attacker has guest access and the guest access settings are set to the default. This technique was actually first demo'd by @DrAzureAD in a blog post here: https://aadinternals.com/post/quest_for_guest/
What are Guest Accounts?
Azure AD lets you invite a guest user from another organization so that you can collaborate with them (business-to-business). By default, anybody with an account can invite a guest—even other guests.
Here is an example of a guest invitation created via the Azure Portal:
The invited user will receive an invitation email.
Once a user accepts the invitation, they are added to the Azure environment and show up in the organization's user list. In this case, if I log into the Acme Computer Company Azure Portal as a normal user, we can find the [email protected] guest account.
Guest Access Overview
Now, let's go back to our guest account ([email protected]). Let's take our guest account and log into the Azure Admin Portal. Then, we will go to the upper right corner, where we can switch Directories to our guest tenant.
Select ‘Switch’ on the guest tenant.
Once switched over, the page will redirect you to the home portal for the host-organization's tenant. Pulling up Entra ID, the first page looks normal. You can see in the upper right-hand corner that we are logged in with our account to the guest tenant.
However, trying to access resources as a guest has less than stellar results.
Going to the Users page shows no users and generates an ‘Insufficient privileges to complete the operation.’ response.
Even searching for a valid user doesn't work here. Nor can you search for yourself.
The Groups page flat-out won't load.
This page, along with all the other areas where users and groups are hidden, seems to indicate that in fact, because of the guest-level access, things are SUPPOSED to be hidden. This is by design.
The Flaw
Poking around in the web interface reveals that there is ONE (1) place where you can see some user details—for your own user. That place is the News Feed on the Entra ID home page. Scroll down a little and you should see it.
As far as I know, this is the only place where you can find a clickable reference to a user profile. Clicking on the user takes us to the profile page, which we can actually see.
Now, if we click on the Groups tab on the left, we can see what groups we are part of.
You can see that Acme Computer Company has a dynamic rule that adds users to an 'All Users' group automatically. Many organizations have some variant of this. Clicking on the group name shows details about the group.
This group has 152 members. We can click on the Members list to see all other members of the group.
In this case, we got lucky and ended up in a group with all users. However, even if we did not, we would likely be able to enumerate many users and groups.
Let's look at the other group that [email protected] is part of: Project X.
If a guest user is going to do anything, they'll likely be part of a group, and that group will be managed by somebody from the parent tenant. There may also be members of the parent tenant in the group itself. In this case, if we look at the owners for the group, we see that we have members of the parent tenant.
Here, we can see that we have a couple of owners. And if we click on them, we can see their profiles.
We can then click on their Group memberships and explore those.
In theory, we should usually be able to snowball our enumeration until we have found all users and the groups that they have access to. This is especially true if any sort of ‘All Users’ group exists—manual or dynamic.
By now, it should be apparent where the flaw is in this scheme. Since our owner (Ana Lopez) is part of the 'All Users' group, we are back to a full user list.
It just took our guest user being a member of one (1) group. Once we found that loose thread—the initial user account—we were then able to unravel the sweater.
Now, this is a small company. You could copy these down by hand, in theory. But what about in real use? What if we had 5,000 users? You can see in the screenshot below that the ‘Bulk operations’ dropdown menu to download members is greyed out.
So—wat do?
The Plan
Obviously, we want to find a way to make a computer program do the work for us. Computers are fantastic at repetitive and precise tasks that humans suck at. We just need to figure out a way to recreate our stepping stones that we had in the web interface.
Our basic pattern is to:
- Look up our own group membership and look for owners or group members from the parent tenant; e.g., ‘Project X - Owner: Ana Lopez’.
- When we find a user, we will look at their group memberships and get lists of all the users in those groups; e.g., ‘Ana Lopez Groups: All Users’.
- We will then look at the users that we find in those groups and look at their other group memberships, essentially going back to Step 1. This pattern can generally be repeated, alternating harvesting group ObjectIds and user ObjectIds, until we hopefully have all the users and groups in the organization.
Automation: Enter PowerShell and AAD Commandlets
PowerShell is a great tool for interacting with Azure AD and M365. Microsoft provides some modules with a wide range of capabilities. I released a tool called o365recon that uses a standard set of credentials to dump username lists, group lists, group membership lists, and more (it will not work with guest users). The PowerShell modules powering that tool are the Azure AD and MsolService modules.
We will be reusing some code from o365recon, and so we are going to stick to the Azure AD commandlet. However, we are going to be using the Az.Auth module this time around (for reasons that I'll explain later). The MsolService module does not support B2B tenant interactions, unfortunately.
Note: This could also likely be done with Graph API entirely, if anybody is looking for a project.
Some Background Info
It's important to know that on the back-end of Azure AD and O365, all users, groups, applications, and devices have an ‘ObjectId’ assigned. This ‘ObjectId’ is a GUID that looks like this:
42fad32e-8453-429d-99ad-03ab5b8c9efc
Using PowerShell, as a guest, if you know the GUID for the ObjectId of an object, you can generally retrieve at least some basic information about the object, be it a user, group, or other.
Unfortunately, a GUID is a long, complex string of numbers and letters. It's meant to resist guessing.
In order to get to our first step (looking up our own group membership), we will first need to get our own user's ObjectId GUID. Oh, and before that, we need to find our tenant ID for our guest organization.
Working Through It
Using the standard Azure AD module, there is no immediately obvious way to get the tenant ID of the organization where you are a guest. You could log into the Azure AD Portal, but we want to do this programmatically.
At this point, make sure you have installed the modules. With an administrator PS window, do:
Install-Module Az.Accounts
Install-Module AzureAD
(https://docs.microsoft.com/en-us/powershell/module/az.accounts/?view=azps-6.5.0)
(https://docs.microsoft.com/en-us/powershell/module/azuread/?view=azureadps-2.0)
First, we will connect to Azure using Connect-AzAccount. Then, we will get our tenant information.
Connect-AzAccount
Get-AzTenant
Here, we can see that Get-AzTenant shows us both our home tenant (Intranet Directory) and the tenant where we have guest access (Acme Computer Company).
We need to make note of the tenant ID. For sake of ease, go ahead and copy the tenant ID into a variable:
$tenantid = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Next, we will request a token and use that to retrieve information about our user. Be sure to specify -TenantId so that we get a token for the guest tenant (Acme Computer Company).
$newtoken = Get-AzAccessToken -TenantId $tenantid -Resource "https://graph.microsoft.com/"
$graphresponse = Invoke-RestMethod https://graph.microsoft.com/v1.0/me -Headers @{Authorization = "Bearer $($newtoken.token)"}
$graphresponse
Here, the id value is our guest user's ObjectId. We need to make note of this. Fantastic. The first snake pit has been crossed.
Note: You may ask, "We're using Graph here—why don't we just use Graph to pull the user list directly?" Well, they have that locked down, too:
Now, back to it—we connect to Azure AD using the ‘Connect-AzureAD’ commandlet, while specifying the flag ‘-TenantId
Connect-AzureAD -TenantId $tenantid
If we test out the ‘Get-AzureADUser’ commandlet while specifying our user's ObjectId, we can see that it does indeed return our user object.
Get-AzureADUser -ObjectId $graphresponse.id
Next, we can use the ‘Get-AzureADUserMembership’ commandlet to see what groups our user is a member of.
Get-AzureADUserMembership -ObjectId $graphresponse.id
Following the path, we then look up the membership list and owner list for the group we identified (All Users).
We can use the commandlets ‘Get-AzureADGroupOwner’ and ‘Get-AzureADGroupMember’ to see who the owners and group members are.
Get-AzureADGroup -ObjectId xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Get-AzureADGroupOwner -ObjectId xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Get-AzureADGroupMember -ObjectId xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
The only people who would not be enumerated would be ones who exist in silos—members of a group, with no cross-group membership, or owners with any groups of affected people. If there is an ‘All Users’ group, it will be especially easy to reach full coverage.
Making the Computer Do the Work
In order to make this easier, and automatic, I've written a script called bad_guest.ps1 that will do all the hard work.
Note: You will have to authenticate twice—once to make the initial requests with your home tenant and then again to auth to the guest tenant.
The script will retrieve group members and owners for any groups your guest user is a member of. It will then spread out and try to enumerate groups and users. When it no longer finds any new groups or users, it will then use those GUIDs to dump information.
This tool is very similar to o365recon, except you will most likely have access to WAY LESS information. It will retrieve the following:
- User list
- Group list
- Group membership list
- Application list
Note: Some details of the user profile information, such as address or other personal information, will be missing unless your guest permissions have been modified from the baseline.
Remediation
After submitting this to Microsoft, they noted that for the default settings, this is not technically a bug, despite their blocking of it in the Azure Portal, etc. They recommended that I restrict guest user access permissions via the External Collaboration Settings.
https://learn.microsoft.com/en-us/entra/identity/users/users-restrict-guest-permissions
Here are the default settings:
For additional security, we want to change to the settings below, which will restrict the guest user to only their own Directory objects.
Now, if we run our bad_guest script again, we can see that it only finds one user’s account—our own—and then errors out when trying to retrieve group lists. This does indeed fix the flaw!
Conclusion
Microsoft makes it clear from the web interface that we should NOT be able to do this. Even with the default settings, guest users are purposely prevented from seeing any of this information in the Azure Portal. However, due to the design of Azure AD, and the permissions that allow a guest user to view individual profiles and group membership if the ObjectId is known, we are able to bypass these restrictions.
While this attack isn't going to be too useful for most penetration testing, it is an example of what a real-life attacker could do to attempt to gain information in order to move laterally into other organizations from the first.
Remember—by default, any guest can invite other guests. And by default, guests can view usernames, group names, and group membership.
Timeline:
10/12/2021 Submitted to MSRC
10/19/2021 MSRC responds, investigates
01/12/2022 MSRC responds, working as intended. Suggest 'Restrict user access permissions' setting in Azure