Published on

Restricting Office 365 PowerShell to members of a Security Group

Authors
  • avatar
    Name
    Jonathan Devere-Ellery
    Twitter
do not enter sign
Photo by Kyle Glenn on Unsplash

Overview

Recently I had a request to secure remote PowerShell in an Office 365 tenant since the default behavior in Office 365 is that even non-admin users will be able to connect a remote PowerShell session to AzureAD or Exchange Online. This customer wanted a way to ensure that only administrators were able to login and perform tasks using PowerShell.

What are the risks?

If a non-admin account tries connecting a PowerShell session by running Connect-AzureAD, they'll be able to login and won't get any sort of error message:

Non-Admin connecting

The scope of permissions for these non-admin accounts will be very limited, but there is still benefit for an attacker. In the same way that PowerShell is great for Admins to automate their work and make their lives easier, an attacker can extend their initial foothold into an environment and speed up their runbooks for reconnaissance or persistence techniques into an environment.

To try and understand what is the real-world impact and how an attacker could abuse it I did some testing in my tenant to see what kinds of commands a non-admin account could run. I was able to successfully run a number of useful commands, and I'm sure there are more but this made it clear that there is definite benefit for an attacker:

Get-AzureADUser -All $true
Get-AzureADGroup -All $true
Get-AzureADGroup -Filter "DisplayName eq 'Executives'" | Get-AzureADGroupMember
Get-AzureADDevice
Get-AzureADDirectoryRole -Filter "DisplayName eq 'Global Administrator'" | Get-AzureADDirectoryRoleMember
Get-Team
Get-Team | Get-TeamChannel
Get-TeamUser -GroupId <GUID>
Get-TeamsApp
New-Team -DisplayName "Test Team"
New-TeamChannel -GroupId <GUID> -DisplayName "New Channel"
Add-TeamUser -GroupId <GUID> -User AdeleV@contoso.com (adding a different user into a Team)
Get-Team -GroupId <GUID> | Set-TeamPicture -ImagePath C:\Temp\contoso.png

How do we fix it?

I've seen it previously suggested to run Set-MsolCompanySettings -UsersPermissionToReadOtherUsersEnabled $false, which will result in locking down the ability to query objects within AzureAD. The first problem with this is that it's a tenant-wide setting so this becomes an all-or-nothing setting. Secondly, and more importantly, configuring this UsersPermissionToReadOtherUsersEnabled setting is known to break certain functionality such as Teams integration and accessing to the Exchange Global Address List. So this isn't going to be a realistic solution for most environments.

Using AzureAD Application Assignments

The alternative is that since all of the PowerShell endpoints in Office 365 have a corresponding AzureAD Application registered for it, what we can instead configure a Role Assignment policies for each of these apps. The policy would make it possible to control each of these apps and only allow certain accounts to access them. Since we can do this using a security group we can be much more granular than making a tenant-wide change.

To achieve the intended goal of restricting PowerShell at the customer we performed the below steps:

  1. Create a Security Group in AzureAD
    • New-AzureADGroup -DisplayName "GroupName-Allowed" -MailEnabled $false -SecurityEnabled $true -MailNickName "NotSet"
  2. (Optional) It's possible that the Service Principal doesn't actually exist yet, so in that case we need to first create it in our tenant
    • New-AzureADServicePrincipal -AppId <AppId>
  3. Retrieve the Service Principal for the Application that we want to restrict
    • Get-AzureADServicePrincipal -Filter "appId eq '<AppId>'"
  4. Modify the Service Principal to enable AppRoleAssignmentRequired
    • Set-AzureADServicePrincipal -ObjectId <ServicePrincipal> -AppRoleAssignmentRequired $true
  5. Create a Role Assignment on the Service Principal to actually control who has access to the app. The New-AzureADServiceAppRoleAssignment Docs page states that the -Id parameter is required but that "if no app roles have been defined to the resource app, you can use 00000000-0000-0000-0000-000000000000 to indicate assignment of the resource app or service, without specifying an app role" which is why we give it an empty GUID.
    • New-AzureADServiceAppRoleAssignment -ObjectId <ServicePrincipal> -ResourceId <ServicePrincipal> -Id ([Guid]::Empty.ToString()) -PrincipalId <GroupName>

Restricting users for AzureAD PowerShell

Putting this all together, if we want to create a restriction on who could login to Azure AD PowerShell module, then we would run the following:

$group = New-AzureADGroup -DisplayName "AAD-PowerShell-Allowed" -MailEnabled $false -SecurityEnabled $true -MailNickName "NotSet"

$appID = "1b730954-1685-4b74-9bfd-dac224a7b894" # // Azure Active Directory PowerShell
New-AzureADServicePrincipal -AppId $appID # Optional, we can skip this if the Service Principal already exists
$sp = Get-AzureADServicePrincipal -Filter "appId eq '$appID'" 
Set-AzureADServicePrincipal -ObjectId $sp.ObjectId -AppRoleAssignmentRequired $true
New-AzureADServiceAppRoleAssignment -ObjectId $sp.ObjectId -ResourceId $sp.ObjectId -Id ([Guid]::Empty.ToString()) -PrincipalId $group.ObjectId

If we want to roll-back the change then we can set the principal to no longer require the Role Assignment:

Set-AzureADServicePrincipal -ObjectId $sp.ObjectId -AppRoleAssignmentRequired $false

The result is that now when a user who is not a member of this security group tries to login to PowerShell by running Connect-AzureAD they will get an error message stating "Your administrator has configured the application to block users unless they are specifically granted ('assigned') access to the application", and will be denied access:

RestrictedLogin

Now they can manage access to PowerShell by simply adding their Administrators into this security group, and every other account will be denied access.

Restricting users for Exchange Online PowerShell

AzureAD isn't the only app that we can restrict, and we can extend this to other resources such as Exchange Online. Keep in mind that EXO has both the EXO Remote PowerShell and Microsoft Exchange REST API Based Powershell apps which cover both the v1 and v2 EXO PowerShell modules, so we need to do this twice.

$group = New-AzureADGroup -DisplayName "EXO-PowerShell-Allowed" -MailEnabled $false -SecurityEnabled $true -MailNickName "NotSet"

$appID = "fb78d390-0c51-40cd-8e17-fdbfab77341b" # // Microsoft Exchange REST API Based Powershell
New-AzureADServicePrincipal -AppId $appID # Optional, we can skip this if the Service Principal already exists
$sp = Get-AzureADServicePrincipal -Filter "appId eq '$appID'" 
Set-AzureADServicePrincipal -ObjectId $sp.ObjectId -AppRoleAssignmentRequired $true
New-AzureADServiceAppRoleAssignment -ObjectId $sp.ObjectId -ResourceId $sp.ObjectId -Id ([Guid]::Empty.ToString()) -PrincipalId $group.ObjectId

$appID = "a0c73c16-a7e3-4564-9a95-2bdf47383716" # // EXO Remote PowerShell
New-AzureADServicePrincipal -AppId $appID # Optional, we can skip this if the Service Principal already exists
$sp = Get-AzureADServicePrincipal -Filter "appId eq '$appID'" 
Set-AzureADServicePrincipal -ObjectId $sp.ObjectId -AppRoleAssignmentRequired $true
New-AzureADServiceAppRoleAssignment -ObjectId $sp.ObjectId -ResourceId $sp.ObjectId -Id ([Guid]::Empty.ToString()) -PrincipalId $group.ObjectId
RestrictedExchangeOnline

Retricting other Office 365 PowerShell Apps

I only covered the basics such as Exchange Online and AzureAD because these represent the greatest risks for most organisations, but it can also be extended to other workloads such as Teams, SharePoint, Azure. All that is required is to find the GUID for the particular app. You can either find the GUIDs for these yourself by looking in the AzureAD Sign-in logs report and then finding the corresponding 'Application ID' for a session, or there are some pre-compiled lists of well-known client ids which can make this easier.

I hope you found this article useful, if you have any questions just drop a comment below.